@livekit/agents 0.7.0 → 0.7.1
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.
- package/dist/cli.cjs +8 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -1
- package/dist/ipc/inference_proc_lazy_main.cjs +2 -0
- package/dist/ipc/inference_proc_lazy_main.cjs.map +1 -1
- package/dist/ipc/inference_proc_lazy_main.js +2 -0
- package/dist/ipc/inference_proc_lazy_main.js.map +1 -1
- package/dist/ipc/job_proc_lazy_main.cjs +5 -0
- package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
- package/dist/ipc/job_proc_lazy_main.js +5 -0
- package/dist/ipc/job_proc_lazy_main.js.map +1 -1
- package/dist/multimodal/agent_playout.cjs +5 -2
- package/dist/multimodal/agent_playout.cjs.map +1 -1
- package/dist/multimodal/agent_playout.d.ts.map +1 -1
- package/dist/multimodal/agent_playout.js +5 -2
- package/dist/multimodal/agent_playout.js.map +1 -1
- package/dist/pipeline/agent_output.cjs +3 -1
- package/dist/pipeline/agent_output.cjs.map +1 -1
- package/dist/pipeline/agent_output.js +3 -1
- package/dist/pipeline/agent_output.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +9 -0
- package/src/ipc/inference_proc_lazy_main.ts +4 -0
- package/src/ipc/job_proc_lazy_main.ts +8 -0
- package/src/multimodal/agent_playout.ts +5 -2
- package/src/pipeline/agent_output.ts +3 -1
package/dist/cli.cjs
CHANGED
|
@@ -50,6 +50,14 @@ const runWorker = async (args) => {
|
|
|
50
50
|
logger.info("worker closed");
|
|
51
51
|
process.exit(130);
|
|
52
52
|
});
|
|
53
|
+
process.once("SIGTERM", async () => {
|
|
54
|
+
if (args.production) {
|
|
55
|
+
await worker.drain();
|
|
56
|
+
}
|
|
57
|
+
await worker.close();
|
|
58
|
+
logger.info("worker closed");
|
|
59
|
+
process.exit(143);
|
|
60
|
+
});
|
|
53
61
|
try {
|
|
54
62
|
await worker.run();
|
|
55
63
|
} catch {
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../../node_modules/.pnpm/tsup@8.3.5_@microsoft+api-extractor@7.43.7_@types+node@22.5.5__postcss@8.4.38_tsx@4.19.2_typescript@5.4.5/node_modules/tsup/assets/cjs_shims.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Command, Option } from 'commander';\nimport type { EventEmitter } from 'node:events';\nimport { initializeLogger, log } from './log.js';\nimport { version } from './version.js';\nimport { Worker, WorkerOptions } from './worker.js';\n\ntype CliArgs = {\n opts: WorkerOptions;\n production: boolean;\n watch: boolean;\n event?: EventEmitter;\n room?: string;\n participantIdentity?: string;\n};\n\nconst runWorker = async (args: CliArgs) => {\n initializeLogger({ pretty: !args.production, level: args.opts.logLevel });\n const logger = log();\n\n // though `production` is defined in WorkerOptions, it will always be overriddden by CLI.\n const { production: _, ...opts } = args.opts; // eslint-disable-line @typescript-eslint/no-unused-vars\n const worker = new Worker(new WorkerOptions({ production: args.production, ...opts }));\n\n if (args.room) {\n worker.event.once('worker_registered', () => {\n logger.info(`connecting to room ${args.room}`);\n worker.simulateJob(args.room!, args.participantIdentity);\n });\n }\n\n process.once('SIGINT', async () => {\n // allow C-c C-c for force interrupt\n process.once('SIGINT', () => {\n logger.info('worker closed forcefully');\n process.exit(130); // SIGINT exit code\n });\n if (args.production) {\n await worker.drain();\n }\n await worker.close();\n logger.info('worker closed');\n process.exit(130); // SIGINT exit code\n });\n\n try {\n await worker.run();\n } catch {\n logger.fatal('worker failed');\n process.exit(1);\n }\n};\n\n/**\n * Exposes a CLI for creating a new worker, in development or production mode.\n *\n * @param opts - Options to launch the worker with\n * @example\n * ```\n * if (process.argv[1] === fileURLToPath(import.meta.url)) {\n * cli.runApp(new WorkerOptions({ agent: import.meta.filename }));\n * }\n * ```\n */\nexport const runApp = (opts: WorkerOptions) => {\n const program = new Command()\n .name('agents')\n .description('LiveKit Agents CLI')\n .version(version)\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('info')\n .env('LOG_LEVEL'),\n )\n .addOption(\n new Option('--url <string>', 'LiveKit server or Cloud project websocket URL').env(\n 'LIVEKIT_URL',\n ),\n )\n .addOption(\n new Option('--api-key <string>', \"LiveKit server or Cloud project's API key\").env(\n 'LIVEKIT_API_KEY',\n ),\n )\n .addOption(\n new Option('--api-secret <string>', \"LiveKit server or Cloud project's API secret\").env(\n 'LIVEKIT_API_SECRET',\n ),\n )\n .action(() => {\n if (\n // do not run CLI if origin file is agents/ipc/job_main.js\n process.argv[1] !== new URL('ipc/job_main.js', import.meta.url).pathname &&\n process.argv.length < 3\n ) {\n program.help();\n }\n });\n\n program\n .command('start')\n .description('Start the worker in production mode')\n .action(() => {\n const options = program.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: true,\n watch: false,\n });\n });\n\n program\n .command('dev')\n .description('Start the worker in development mode')\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('debug')\n .env('LOG_LEVEL'),\n )\n .action(() => {\n const options = program.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: false,\n watch: false,\n });\n });\n\n program\n .command('connect')\n .description('Connect to a specific room')\n .requiredOption('--room <string>', 'Room name to connect to')\n .option('--participant-identity <string>', 'Identity of user to listen to')\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('debug')\n .env('LOG_LEVEL'),\n )\n .action((...[, command]) => {\n const options = command.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: false,\n watch: false,\n room: options.room,\n participantIdentity: options.participantIdentity,\n });\n });\n\n program.parse();\n};\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () =>\n typeof document === 'undefined'\n ? new URL(`file:${__filename}`).href\n : (document.currentScript && document.currentScript.src) ||\n new URL('main.js', document.baseURI).href\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../../node_modules/.pnpm/tsup@8.3.5_@microsoft+api-extractor@7.43.7_@types+node@22.5.5__postcss@8.4.38_tsx@4.19.2_typescript@5.4.5/node_modules/tsup/assets/cjs_shims.js"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Command, Option } from 'commander';\nimport type { EventEmitter } from 'node:events';\nimport { initializeLogger, log } from './log.js';\nimport { version } from './version.js';\nimport { Worker, WorkerOptions } from './worker.js';\n\ntype CliArgs = {\n opts: WorkerOptions;\n production: boolean;\n watch: boolean;\n event?: EventEmitter;\n room?: string;\n participantIdentity?: string;\n};\n\nconst runWorker = async (args: CliArgs) => {\n initializeLogger({ pretty: !args.production, level: args.opts.logLevel });\n const logger = log();\n\n // though `production` is defined in WorkerOptions, it will always be overriddden by CLI.\n const { production: _, ...opts } = args.opts; // eslint-disable-line @typescript-eslint/no-unused-vars\n const worker = new Worker(new WorkerOptions({ production: args.production, ...opts }));\n\n if (args.room) {\n worker.event.once('worker_registered', () => {\n logger.info(`connecting to room ${args.room}`);\n worker.simulateJob(args.room!, args.participantIdentity);\n });\n }\n\n process.once('SIGINT', async () => {\n // allow C-c C-c for force interrupt\n process.once('SIGINT', () => {\n logger.info('worker closed forcefully');\n process.exit(130); // SIGINT exit code\n });\n if (args.production) {\n await worker.drain();\n }\n await worker.close();\n logger.info('worker closed');\n process.exit(130); // SIGINT exit code\n });\n\n process.once('SIGTERM', async () => {\n if (args.production) {\n await worker.drain();\n }\n await worker.close();\n logger.info('worker closed');\n process.exit(143); // SIGTERM exit code\n });\n\n try {\n await worker.run();\n } catch {\n logger.fatal('worker failed');\n process.exit(1);\n }\n};\n\n/**\n * Exposes a CLI for creating a new worker, in development or production mode.\n *\n * @param opts - Options to launch the worker with\n * @example\n * ```\n * if (process.argv[1] === fileURLToPath(import.meta.url)) {\n * cli.runApp(new WorkerOptions({ agent: import.meta.filename }));\n * }\n * ```\n */\nexport const runApp = (opts: WorkerOptions) => {\n const program = new Command()\n .name('agents')\n .description('LiveKit Agents CLI')\n .version(version)\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('info')\n .env('LOG_LEVEL'),\n )\n .addOption(\n new Option('--url <string>', 'LiveKit server or Cloud project websocket URL').env(\n 'LIVEKIT_URL',\n ),\n )\n .addOption(\n new Option('--api-key <string>', \"LiveKit server or Cloud project's API key\").env(\n 'LIVEKIT_API_KEY',\n ),\n )\n .addOption(\n new Option('--api-secret <string>', \"LiveKit server or Cloud project's API secret\").env(\n 'LIVEKIT_API_SECRET',\n ),\n )\n .action(() => {\n if (\n // do not run CLI if origin file is agents/ipc/job_main.js\n process.argv[1] !== new URL('ipc/job_main.js', import.meta.url).pathname &&\n process.argv.length < 3\n ) {\n program.help();\n }\n });\n\n program\n .command('start')\n .description('Start the worker in production mode')\n .action(() => {\n const options = program.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: true,\n watch: false,\n });\n });\n\n program\n .command('dev')\n .description('Start the worker in development mode')\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('debug')\n .env('LOG_LEVEL'),\n )\n .action(() => {\n const options = program.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: false,\n watch: false,\n });\n });\n\n program\n .command('connect')\n .description('Connect to a specific room')\n .requiredOption('--room <string>', 'Room name to connect to')\n .option('--participant-identity <string>', 'Identity of user to listen to')\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('debug')\n .env('LOG_LEVEL'),\n )\n .action((...[, command]) => {\n const options = command.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: false,\n watch: false,\n room: options.room,\n participantIdentity: options.participantIdentity,\n });\n });\n\n program.parse();\n};\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () =>\n typeof document === 'undefined'\n ? new URL(`file:${__filename}`).href\n : (document.currentScript && document.currentScript.src) ||\n new URL('main.js', document.baseURI).href\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n"],"mappings":"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","names":[]}
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAOA,OAAO,EAAU,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAOA,OAAO,EAAU,aAAa,EAAE,MAAM,aAAa,CAAC;AAyDpD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,MAAM,SAAU,aAAa,SAqGzC,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -25,6 +25,14 @@ const runWorker = async (args) => {
|
|
|
25
25
|
logger.info("worker closed");
|
|
26
26
|
process.exit(130);
|
|
27
27
|
});
|
|
28
|
+
process.once("SIGTERM", async () => {
|
|
29
|
+
if (args.production) {
|
|
30
|
+
await worker.drain();
|
|
31
|
+
}
|
|
32
|
+
await worker.close();
|
|
33
|
+
logger.info("worker closed");
|
|
34
|
+
process.exit(143);
|
|
35
|
+
});
|
|
28
36
|
try {
|
|
29
37
|
await worker.run();
|
|
30
38
|
} catch {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Command, Option } from 'commander';\nimport type { EventEmitter } from 'node:events';\nimport { initializeLogger, log } from './log.js';\nimport { version } from './version.js';\nimport { Worker, WorkerOptions } from './worker.js';\n\ntype CliArgs = {\n opts: WorkerOptions;\n production: boolean;\n watch: boolean;\n event?: EventEmitter;\n room?: string;\n participantIdentity?: string;\n};\n\nconst runWorker = async (args: CliArgs) => {\n initializeLogger({ pretty: !args.production, level: args.opts.logLevel });\n const logger = log();\n\n // though `production` is defined in WorkerOptions, it will always be overriddden by CLI.\n const { production: _, ...opts } = args.opts; // eslint-disable-line @typescript-eslint/no-unused-vars\n const worker = new Worker(new WorkerOptions({ production: args.production, ...opts }));\n\n if (args.room) {\n worker.event.once('worker_registered', () => {\n logger.info(`connecting to room ${args.room}`);\n worker.simulateJob(args.room!, args.participantIdentity);\n });\n }\n\n process.once('SIGINT', async () => {\n // allow C-c C-c for force interrupt\n process.once('SIGINT', () => {\n logger.info('worker closed forcefully');\n process.exit(130); // SIGINT exit code\n });\n if (args.production) {\n await worker.drain();\n }\n await worker.close();\n logger.info('worker closed');\n process.exit(130); // SIGINT exit code\n });\n\n try {\n await worker.run();\n } catch {\n logger.fatal('worker failed');\n process.exit(1);\n }\n};\n\n/**\n * Exposes a CLI for creating a new worker, in development or production mode.\n *\n * @param opts - Options to launch the worker with\n * @example\n * ```\n * if (process.argv[1] === fileURLToPath(import.meta.url)) {\n * cli.runApp(new WorkerOptions({ agent: import.meta.filename }));\n * }\n * ```\n */\nexport const runApp = (opts: WorkerOptions) => {\n const program = new Command()\n .name('agents')\n .description('LiveKit Agents CLI')\n .version(version)\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('info')\n .env('LOG_LEVEL'),\n )\n .addOption(\n new Option('--url <string>', 'LiveKit server or Cloud project websocket URL').env(\n 'LIVEKIT_URL',\n ),\n )\n .addOption(\n new Option('--api-key <string>', \"LiveKit server or Cloud project's API key\").env(\n 'LIVEKIT_API_KEY',\n ),\n )\n .addOption(\n new Option('--api-secret <string>', \"LiveKit server or Cloud project's API secret\").env(\n 'LIVEKIT_API_SECRET',\n ),\n )\n .action(() => {\n if (\n // do not run CLI if origin file is agents/ipc/job_main.js\n process.argv[1] !== new URL('ipc/job_main.js', import.meta.url).pathname &&\n process.argv.length < 3\n ) {\n program.help();\n }\n });\n\n program\n .command('start')\n .description('Start the worker in production mode')\n .action(() => {\n const options = program.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: true,\n watch: false,\n });\n });\n\n program\n .command('dev')\n .description('Start the worker in development mode')\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('debug')\n .env('LOG_LEVEL'),\n )\n .action(() => {\n const options = program.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: false,\n watch: false,\n });\n });\n\n program\n .command('connect')\n .description('Connect to a specific room')\n .requiredOption('--room <string>', 'Room name to connect to')\n .option('--participant-identity <string>', 'Identity of user to listen to')\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('debug')\n .env('LOG_LEVEL'),\n )\n .action((...[, command]) => {\n const options = command.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: false,\n watch: false,\n room: options.room,\n participantIdentity: options.participantIdentity,\n });\n });\n\n program.parse();\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { Command, Option } from 'commander';\nimport type { EventEmitter } from 'node:events';\nimport { initializeLogger, log } from './log.js';\nimport { version } from './version.js';\nimport { Worker, WorkerOptions } from './worker.js';\n\ntype CliArgs = {\n opts: WorkerOptions;\n production: boolean;\n watch: boolean;\n event?: EventEmitter;\n room?: string;\n participantIdentity?: string;\n};\n\nconst runWorker = async (args: CliArgs) => {\n initializeLogger({ pretty: !args.production, level: args.opts.logLevel });\n const logger = log();\n\n // though `production` is defined in WorkerOptions, it will always be overriddden by CLI.\n const { production: _, ...opts } = args.opts; // eslint-disable-line @typescript-eslint/no-unused-vars\n const worker = new Worker(new WorkerOptions({ production: args.production, ...opts }));\n\n if (args.room) {\n worker.event.once('worker_registered', () => {\n logger.info(`connecting to room ${args.room}`);\n worker.simulateJob(args.room!, args.participantIdentity);\n });\n }\n\n process.once('SIGINT', async () => {\n // allow C-c C-c for force interrupt\n process.once('SIGINT', () => {\n logger.info('worker closed forcefully');\n process.exit(130); // SIGINT exit code\n });\n if (args.production) {\n await worker.drain();\n }\n await worker.close();\n logger.info('worker closed');\n process.exit(130); // SIGINT exit code\n });\n\n process.once('SIGTERM', async () => {\n if (args.production) {\n await worker.drain();\n }\n await worker.close();\n logger.info('worker closed');\n process.exit(143); // SIGTERM exit code\n });\n\n try {\n await worker.run();\n } catch {\n logger.fatal('worker failed');\n process.exit(1);\n }\n};\n\n/**\n * Exposes a CLI for creating a new worker, in development or production mode.\n *\n * @param opts - Options to launch the worker with\n * @example\n * ```\n * if (process.argv[1] === fileURLToPath(import.meta.url)) {\n * cli.runApp(new WorkerOptions({ agent: import.meta.filename }));\n * }\n * ```\n */\nexport const runApp = (opts: WorkerOptions) => {\n const program = new Command()\n .name('agents')\n .description('LiveKit Agents CLI')\n .version(version)\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('info')\n .env('LOG_LEVEL'),\n )\n .addOption(\n new Option('--url <string>', 'LiveKit server or Cloud project websocket URL').env(\n 'LIVEKIT_URL',\n ),\n )\n .addOption(\n new Option('--api-key <string>', \"LiveKit server or Cloud project's API key\").env(\n 'LIVEKIT_API_KEY',\n ),\n )\n .addOption(\n new Option('--api-secret <string>', \"LiveKit server or Cloud project's API secret\").env(\n 'LIVEKIT_API_SECRET',\n ),\n )\n .action(() => {\n if (\n // do not run CLI if origin file is agents/ipc/job_main.js\n process.argv[1] !== new URL('ipc/job_main.js', import.meta.url).pathname &&\n process.argv.length < 3\n ) {\n program.help();\n }\n });\n\n program\n .command('start')\n .description('Start the worker in production mode')\n .action(() => {\n const options = program.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: true,\n watch: false,\n });\n });\n\n program\n .command('dev')\n .description('Start the worker in development mode')\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('debug')\n .env('LOG_LEVEL'),\n )\n .action(() => {\n const options = program.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: false,\n watch: false,\n });\n });\n\n program\n .command('connect')\n .description('Connect to a specific room')\n .requiredOption('--room <string>', 'Room name to connect to')\n .option('--participant-identity <string>', 'Identity of user to listen to')\n .addOption(\n new Option('--log-level <level>', 'Set the logging level')\n .choices(['trace', 'debug', 'info', 'warn', 'error', 'fatal'])\n .default('debug')\n .env('LOG_LEVEL'),\n )\n .action((...[, command]) => {\n const options = command.optsWithGlobals();\n opts.wsURL = options.url || opts.wsURL;\n opts.apiKey = options.apiKey || opts.apiKey;\n opts.apiSecret = options.apiSecret || opts.apiSecret;\n opts.logLevel = options.logLevel || opts.logLevel;\n runWorker({\n opts,\n production: false,\n watch: false,\n room: options.room,\n participantIdentity: options.participantIdentity,\n });\n });\n\n program.parse();\n};\n"],"mappings":"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","names":[]}
|
|
@@ -28,6 +28,8 @@ const ORPHANED_TIMEOUT = 15 * 1e3;
|
|
|
28
28
|
if (process.send) {
|
|
29
29
|
process.on("SIGINT", () => {
|
|
30
30
|
});
|
|
31
|
+
process.on("SIGTERM", () => {
|
|
32
|
+
});
|
|
31
33
|
await (0, import_node_events.once)(process, "message").then(([msg]) => {
|
|
32
34
|
msg = msg;
|
|
33
35
|
if (msg.case !== "initializeRequest") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ipc/inference_proc_lazy_main.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport EventEmitter, { once } from 'node:events';\nimport type { InferenceRunner } from '../inference_runner.js';\nimport { initializeLogger, log } from '../log.js';\nimport type { IPCMessage } from './message.js';\n\nconst ORPHANED_TIMEOUT = 15 * 1000;\n\n(async () => {\n if (process.send) {\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\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 logger = log().child({ pid: process.pid });\n\n const runners: { [id: string]: InferenceRunner } = await Promise.all(\n Object.entries(JSON.parse(process.argv[2]!)).map(async ([k, v]) => {\n return [k, await import(v as string).then((m) => new m.default())];\n }),\n ).then(Object.fromEntries);\n\n await Promise.all(\n Object.entries(runners).map(async ([runner, v]) => {\n logger.child({ runner }).debug('initializing inference runner');\n await v.initialize();\n }),\n );\n logger.debug('all inference runners initialized');\n process.send({ case: 'initializeResponse' });\n\n const closeEvent = new EventEmitter();\n\n const orphanedTimeout = setTimeout(() => {\n logger.warn('process orphaned, shutting down');\n process.exit();\n }, ORPHANED_TIMEOUT);\n\n const handleInferenceRequest = async ({\n method,\n requestId,\n data,\n }: {\n method: string;\n requestId: string;\n data: unknown;\n }) => {\n if (!runners[method]) {\n logger.child({ method }).warn('unknown inference method');\n }\n\n try {\n const resp = await runners[method]!.run(data);\n process.send!({ case: 'inferenceResponse', value: { requestId, data: resp } });\n } catch (error) {\n process.send!({ case: 'inferenceResponse', value: { requestId, error } });\n }\n };\n\n process.on('message', (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 case 'shutdownRequest':\n closeEvent.emit('close');\n break;\n case 'inferenceRequest':\n handleInferenceRequest(msg.value);\n }\n });\n }\n})();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAGA,yBAAmC;AAEnC,iBAAsC;AAGtC,MAAM,mBAAmB,KAAK;AAAA,CAE7B,YAAY;AACX,MAAI,QAAQ,MAAM;AAGhB,YAAQ,GAAG,UAAU,MAAM;AAAA,IAAC,CAAC;
|
|
1
|
+
{"version":3,"sources":["../../src/ipc/inference_proc_lazy_main.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport EventEmitter, { once } from 'node:events';\nimport type { InferenceRunner } from '../inference_runner.js';\nimport { initializeLogger, log } from '../log.js';\nimport type { IPCMessage } from './message.js';\n\nconst ORPHANED_TIMEOUT = 15 * 1000;\n\n(async () => {\n if (process.send) {\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\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\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 logger = log().child({ pid: process.pid });\n\n const runners: { [id: string]: InferenceRunner } = await Promise.all(\n Object.entries(JSON.parse(process.argv[2]!)).map(async ([k, v]) => {\n return [k, await import(v as string).then((m) => new m.default())];\n }),\n ).then(Object.fromEntries);\n\n await Promise.all(\n Object.entries(runners).map(async ([runner, v]) => {\n logger.child({ runner }).debug('initializing inference runner');\n await v.initialize();\n }),\n );\n logger.debug('all inference runners initialized');\n process.send({ case: 'initializeResponse' });\n\n const closeEvent = new EventEmitter();\n\n const orphanedTimeout = setTimeout(() => {\n logger.warn('process orphaned, shutting down');\n process.exit();\n }, ORPHANED_TIMEOUT);\n\n const handleInferenceRequest = async ({\n method,\n requestId,\n data,\n }: {\n method: string;\n requestId: string;\n data: unknown;\n }) => {\n if (!runners[method]) {\n logger.child({ method }).warn('unknown inference method');\n }\n\n try {\n const resp = await runners[method]!.run(data);\n process.send!({ case: 'inferenceResponse', value: { requestId, data: resp } });\n } catch (error) {\n process.send!({ case: 'inferenceResponse', value: { requestId, error } });\n }\n };\n\n process.on('message', (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 case 'shutdownRequest':\n closeEvent.emit('close');\n break;\n case 'inferenceRequest':\n handleInferenceRequest(msg.value);\n }\n });\n }\n})();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAGA,yBAAmC;AAEnC,iBAAsC;AAGtC,MAAM,mBAAmB,KAAK;AAAA,CAE7B,YAAY;AACX,MAAI,QAAQ,MAAM;AAGhB,YAAQ,GAAG,UAAU,MAAM;AAAA,IAAC,CAAC;AAI7B,YAAQ,GAAG,WAAW,MAAM;AAAA,IAAC,CAAC;AAE9B,cAAM,yBAAK,SAAS,SAAS,EAAE,KAAK,CAAC,CAAC,GAAG,MAAoB;AAC3D,YAAM;AACN,UAAI,IAAI,SAAS,qBAAqB;AACpC,cAAM,IAAI,MAAM,yCAAyC;AAAA,MAC3D;AACA,uCAAiB,IAAI,MAAM,aAAa;AAAA,IAC1C,CAAC;AACD,UAAM,aAAS,gBAAI,EAAE,MAAM,EAAE,KAAK,QAAQ,IAAI,CAAC;AAE/C,UAAM,UAA6C,MAAM,QAAQ;AAAA,MAC/D,OAAO,QAAQ,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAE,CAAC,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM;AACjE,eAAO,CAAC,GAAG,MAAM,OAAO,GAAa,KAAK,CAAC,MAAM,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,MACnE,CAAC;AAAA,IACH,EAAE,KAAK,OAAO,WAAW;AAEzB,UAAM,QAAQ;AAAA,MACZ,OAAO,QAAQ,OAAO,EAAE,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM;AACjD,eAAO,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,+BAA+B;AAC9D,cAAM,EAAE,WAAW;AAAA,MACrB,CAAC;AAAA,IACH;AACA,WAAO,MAAM,mCAAmC;AAChD,YAAQ,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE3C,UAAM,aAAa,IAAI,mBAAAA,QAAa;AAEpC,UAAM,kBAAkB,WAAW,MAAM;AACvC,aAAO,KAAK,iCAAiC;AAC7C,cAAQ,KAAK;AAAA,IACf,GAAG,gBAAgB;AAEnB,UAAM,yBAAyB,OAAO;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAIM;AACJ,UAAI,CAAC,QAAQ,MAAM,GAAG;AACpB,eAAO,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,0BAA0B;AAAA,MAC1D;AAEA,UAAI;AACF,cAAM,OAAO,MAAM,QAAQ,MAAM,EAAG,IAAI,IAAI;AAC5C,gBAAQ,KAAM,EAAE,MAAM,qBAAqB,OAAO,EAAE,WAAW,MAAM,KAAK,EAAE,CAAC;AAAA,MAC/E,SAAS,OAAO;AACd,gBAAQ,KAAM,EAAE,MAAM,qBAAqB,OAAO,EAAE,WAAW,MAAM,EAAE,CAAC;AAAA,MAC1E;AAAA,IACF;AAEA,YAAQ,GAAG,WAAW,CAAC,QAAoB;AACzC,cAAQ,IAAI,MAAM;AAAA,QAChB,KAAK;AACH,0BAAgB,QAAQ;AACxB,kBAAQ,KAAM;AAAA,YACZ,MAAM;AAAA,YACN,OAAO,EAAE,eAAe,IAAI,MAAM,WAAW,WAAW,KAAK,IAAI,EAAE;AAAA,UACrE,CAAC;AACD;AAAA,QACF,KAAK;AACH,qBAAW,KAAK,OAAO;AACvB;AAAA,QACF,KAAK;AACH,iCAAuB,IAAI,KAAK;AAAA,MACpC;AAAA,IACF,CAAC;AAAA,EACH;AACF,GAAG;","names":["EventEmitter"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ipc/inference_proc_lazy_main.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport EventEmitter, { once } from 'node:events';\nimport type { InferenceRunner } from '../inference_runner.js';\nimport { initializeLogger, log } from '../log.js';\nimport type { IPCMessage } from './message.js';\n\nconst ORPHANED_TIMEOUT = 15 * 1000;\n\n(async () => {\n if (process.send) {\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\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 logger = log().child({ pid: process.pid });\n\n const runners: { [id: string]: InferenceRunner } = await Promise.all(\n Object.entries(JSON.parse(process.argv[2]!)).map(async ([k, v]) => {\n return [k, await import(v as string).then((m) => new m.default())];\n }),\n ).then(Object.fromEntries);\n\n await Promise.all(\n Object.entries(runners).map(async ([runner, v]) => {\n logger.child({ runner }).debug('initializing inference runner');\n await v.initialize();\n }),\n );\n logger.debug('all inference runners initialized');\n process.send({ case: 'initializeResponse' });\n\n const closeEvent = new EventEmitter();\n\n const orphanedTimeout = setTimeout(() => {\n logger.warn('process orphaned, shutting down');\n process.exit();\n }, ORPHANED_TIMEOUT);\n\n const handleInferenceRequest = async ({\n method,\n requestId,\n data,\n }: {\n method: string;\n requestId: string;\n data: unknown;\n }) => {\n if (!runners[method]) {\n logger.child({ method }).warn('unknown inference method');\n }\n\n try {\n const resp = await runners[method]!.run(data);\n process.send!({ case: 'inferenceResponse', value: { requestId, data: resp } });\n } catch (error) {\n process.send!({ case: 'inferenceResponse', value: { requestId, error } });\n }\n };\n\n process.on('message', (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 case 'shutdownRequest':\n closeEvent.emit('close');\n break;\n case 'inferenceRequest':\n handleInferenceRequest(msg.value);\n }\n });\n }\n})();\n"],"mappings":"AAGA,OAAO,gBAAgB,YAAY;AAEnC,SAAS,kBAAkB,WAAW;AAGtC,MAAM,mBAAmB,KAAK;AAAA,CAE7B,YAAY;AACX,MAAI,QAAQ,MAAM;AAGhB,YAAQ,GAAG,UAAU,MAAM;AAAA,IAAC,CAAC;
|
|
1
|
+
{"version":3,"sources":["../../src/ipc/inference_proc_lazy_main.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport EventEmitter, { once } from 'node:events';\nimport type { InferenceRunner } from '../inference_runner.js';\nimport { initializeLogger, log } from '../log.js';\nimport type { IPCMessage } from './message.js';\n\nconst ORPHANED_TIMEOUT = 15 * 1000;\n\n(async () => {\n if (process.send) {\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\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\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 logger = log().child({ pid: process.pid });\n\n const runners: { [id: string]: InferenceRunner } = await Promise.all(\n Object.entries(JSON.parse(process.argv[2]!)).map(async ([k, v]) => {\n return [k, await import(v as string).then((m) => new m.default())];\n }),\n ).then(Object.fromEntries);\n\n await Promise.all(\n Object.entries(runners).map(async ([runner, v]) => {\n logger.child({ runner }).debug('initializing inference runner');\n await v.initialize();\n }),\n );\n logger.debug('all inference runners initialized');\n process.send({ case: 'initializeResponse' });\n\n const closeEvent = new EventEmitter();\n\n const orphanedTimeout = setTimeout(() => {\n logger.warn('process orphaned, shutting down');\n process.exit();\n }, ORPHANED_TIMEOUT);\n\n const handleInferenceRequest = async ({\n method,\n requestId,\n data,\n }: {\n method: string;\n requestId: string;\n data: unknown;\n }) => {\n if (!runners[method]) {\n logger.child({ method }).warn('unknown inference method');\n }\n\n try {\n const resp = await runners[method]!.run(data);\n process.send!({ case: 'inferenceResponse', value: { requestId, data: resp } });\n } catch (error) {\n process.send!({ case: 'inferenceResponse', value: { requestId, error } });\n }\n };\n\n process.on('message', (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 case 'shutdownRequest':\n closeEvent.emit('close');\n break;\n case 'inferenceRequest':\n handleInferenceRequest(msg.value);\n }\n });\n }\n})();\n"],"mappings":"AAGA,OAAO,gBAAgB,YAAY;AAEnC,SAAS,kBAAkB,WAAW;AAGtC,MAAM,mBAAmB,KAAK;AAAA,CAE7B,YAAY;AACX,MAAI,QAAQ,MAAM;AAGhB,YAAQ,GAAG,UAAU,MAAM;AAAA,IAAC,CAAC;AAI7B,YAAQ,GAAG,WAAW,MAAM;AAAA,IAAC,CAAC;AAE9B,UAAM,KAAK,SAAS,SAAS,EAAE,KAAK,CAAC,CAAC,GAAG,MAAoB;AAC3D,YAAM;AACN,UAAI,IAAI,SAAS,qBAAqB;AACpC,cAAM,IAAI,MAAM,yCAAyC;AAAA,MAC3D;AACA,uBAAiB,IAAI,MAAM,aAAa;AAAA,IAC1C,CAAC;AACD,UAAM,SAAS,IAAI,EAAE,MAAM,EAAE,KAAK,QAAQ,IAAI,CAAC;AAE/C,UAAM,UAA6C,MAAM,QAAQ;AAAA,MAC/D,OAAO,QAAQ,KAAK,MAAM,QAAQ,KAAK,CAAC,CAAE,CAAC,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM;AACjE,eAAO,CAAC,GAAG,MAAM,OAAO,GAAa,KAAK,CAAC,MAAM,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,MACnE,CAAC;AAAA,IACH,EAAE,KAAK,OAAO,WAAW;AAEzB,UAAM,QAAQ;AAAA,MACZ,OAAO,QAAQ,OAAO,EAAE,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM;AACjD,eAAO,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,+BAA+B;AAC9D,cAAM,EAAE,WAAW;AAAA,MACrB,CAAC;AAAA,IACH;AACA,WAAO,MAAM,mCAAmC;AAChD,YAAQ,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE3C,UAAM,aAAa,IAAI,aAAa;AAEpC,UAAM,kBAAkB,WAAW,MAAM;AACvC,aAAO,KAAK,iCAAiC;AAC7C,cAAQ,KAAK;AAAA,IACf,GAAG,gBAAgB;AAEnB,UAAM,yBAAyB,OAAO;AAAA,MACpC;AAAA,MACA;AAAA,MACA;AAAA,IACF,MAIM;AACJ,UAAI,CAAC,QAAQ,MAAM,GAAG;AACpB,eAAO,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,0BAA0B;AAAA,MAC1D;AAEA,UAAI;AACF,cAAM,OAAO,MAAM,QAAQ,MAAM,EAAG,IAAI,IAAI;AAC5C,gBAAQ,KAAM,EAAE,MAAM,qBAAqB,OAAO,EAAE,WAAW,MAAM,KAAK,EAAE,CAAC;AAAA,MAC/E,SAAS,OAAO;AACd,gBAAQ,KAAM,EAAE,MAAM,qBAAqB,OAAO,EAAE,WAAW,MAAM,EAAE,CAAC;AAAA,MAC1E;AAAA,IACF;AAEA,YAAQ,GAAG,WAAW,CAAC,QAAoB;AACzC,cAAQ,IAAI,MAAM;AAAA,QAChB,KAAK;AACH,0BAAgB,QAAQ;AACxB,kBAAQ,KAAM;AAAA,YACZ,MAAM;AAAA,YACN,OAAO,EAAE,eAAe,IAAI,MAAM,WAAW,WAAW,KAAK,IAAI,EAAE;AAAA,UACrE,CAAC;AACD;AAAA,QACF,KAAK;AACH,qBAAW,KAAK,OAAO;AACvB;AAAA,QACF,KAAK;AACH,iCAAuB,IAAI,KAAK;AAAA,MACpC;AAAA,IACF,CAAC;AAAA,EACH;AACF,GAAG;","names":[]}
|
|
@@ -101,6 +101,8 @@ const startJob = (proc, func, info, closeEvent, logger) => {
|
|
|
101
101
|
}
|
|
102
102
|
process.on("SIGINT", () => {
|
|
103
103
|
});
|
|
104
|
+
process.on("SIGTERM", () => {
|
|
105
|
+
});
|
|
104
106
|
await (0, import_node_events.once)(process, "message").then(([msg]) => {
|
|
105
107
|
msg = msg;
|
|
106
108
|
if (msg.case !== "initializeRequest") {
|
|
@@ -110,6 +112,9 @@ const startJob = (proc, func, info, closeEvent, logger) => {
|
|
|
110
112
|
});
|
|
111
113
|
const proc = new import_job.JobProcess();
|
|
112
114
|
let logger = (0, import_log.log)().child({ pid: proc.pid });
|
|
115
|
+
process.on("unhandledRejection", (reason) => {
|
|
116
|
+
logger.error(reason);
|
|
117
|
+
});
|
|
113
118
|
logger.debug("initializing job runner");
|
|
114
119
|
agent.prewarm(proc);
|
|
115
120
|
logger.debug("job runner initialized");
|
|
@@ -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 { 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): JobTask => {\n let connect = false;\n let shutdown = false;\n\n const room = new Room();\n room.on(RoomEvent.Disconnected, () => {\n closeEvent.emit('close', false);\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 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(() => logger.error('error while shutting down the job'));\n\n process.send!({ case: 'done' });\n process.exit();\n });\n\n return { ctx, task };\n};\n\n(async () => {\n if (process.send) {\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\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 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('process orphaned, shutting down');\n process.exit();\n }, ORPHANED_TIMEOUT);\n\n process.on('message', (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);\n logger.debug('job started');\n break;\n }\n case 'shutdownRequest': {\n if (!job) {\n break;\n }\n closeEvent.emit('close', '');\n }\n }\n });\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 { 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 { 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): JobTask => {\n let connect = false;\n let shutdown = false;\n\n const room = new Room();\n room.on(RoomEvent.Disconnected, () => {\n closeEvent.emit('close', false);\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 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(() => logger.error('error while shutting down the job'));\n\n process.send!({ case: 'done' });\n process.exit();\n });\n\n return { ctx, task };\n};\n\n(async () => {\n if (process.send) {\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\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\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('process orphaned, shutting down');\n process.exit();\n }, ORPHANED_TIMEOUT);\n\n process.on('message', (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);\n logger.debug('job started');\n break;\n }\n case 'shutdownRequest': {\n if (!job) {\n break;\n }\n closeEvent.emit('close', '');\n }\n }\n });\n }\n})();\n"],"mappings":"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","names":["module","agent"]}
|
|
@@ -100,6 +100,8 @@ const startJob = (proc, func, info, closeEvent, logger) => {
|
|
|
100
100
|
}
|
|
101
101
|
process.on("SIGINT", () => {
|
|
102
102
|
});
|
|
103
|
+
process.on("SIGTERM", () => {
|
|
104
|
+
});
|
|
103
105
|
await once(process, "message").then(([msg]) => {
|
|
104
106
|
msg = msg;
|
|
105
107
|
if (msg.case !== "initializeRequest") {
|
|
@@ -109,6 +111,9 @@ const startJob = (proc, func, info, closeEvent, logger) => {
|
|
|
109
111
|
});
|
|
110
112
|
const proc = new JobProcess();
|
|
111
113
|
let logger = log().child({ pid: proc.pid });
|
|
114
|
+
process.on("unhandledRejection", (reason) => {
|
|
115
|
+
logger.error(reason);
|
|
116
|
+
});
|
|
112
117
|
logger.debug("initializing job runner");
|
|
113
118
|
agent.prewarm(proc);
|
|
114
119
|
logger.debug("job runner initialized");
|
|
@@ -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 { 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): JobTask => {\n let connect = false;\n let shutdown = false;\n\n const room = new Room();\n room.on(RoomEvent.Disconnected, () => {\n closeEvent.emit('close', false);\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 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(() => logger.error('error while shutting down the job'));\n\n process.send!({ case: 'done' });\n process.exit();\n });\n\n return { ctx, task };\n};\n\n(async () => {\n if (process.send) {\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\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 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('process orphaned, shutting down');\n process.exit();\n }, ORPHANED_TIMEOUT);\n\n process.on('message', (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);\n logger.debug('job started');\n break;\n }\n case 'shutdownRequest': {\n if (!job) {\n break;\n }\n closeEvent.emit('close', '');\n }\n }\n });\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 { 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 { 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): JobTask => {\n let connect = false;\n let shutdown = false;\n\n const room = new Room();\n room.on(RoomEvent.Disconnected, () => {\n closeEvent.emit('close', false);\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 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(() => logger.error('error while shutting down the job'));\n\n process.send!({ case: 'done' });\n process.exit();\n });\n\n return { ctx, task };\n};\n\n(async () => {\n if (process.send) {\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\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\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('process orphaned, shutting down');\n process.exit();\n }, ORPHANED_TIMEOUT);\n\n process.on('message', (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);\n logger.debug('job started');\n break;\n }\n case 'shutdownRequest': {\n if (!job) {\n break;\n }\n closeEvent.emit('close', '');\n }\n }\n });\n }\n})();\n"],"mappings":"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","names":["agent"]}
|
|
@@ -65,8 +65,11 @@ class PlayoutHandle extends import_node_events.EventEmitter {
|
|
|
65
65
|
if (this.totalPlayedTime !== void 0) {
|
|
66
66
|
return Math.floor(this.totalPlayedTime * this.#sampleRate);
|
|
67
67
|
}
|
|
68
|
-
return Math.
|
|
69
|
-
|
|
68
|
+
return Math.max(
|
|
69
|
+
0,
|
|
70
|
+
Math.floor(
|
|
71
|
+
(this.pushedDuration - this.#audioSource.queuedDuration) * (this.#sampleRate / 1e3)
|
|
72
|
+
)
|
|
70
73
|
);
|
|
71
74
|
}
|
|
72
75
|
get textChars() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/multimodal/agent_playout.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { type AudioSource } from '@livekit/rtc-node';\nimport { EventEmitter } from 'node:events';\nimport { AudioByteStream } from '../audio.js';\nimport type { TextAudioSynchronizer } from '../transcription.js';\nimport { type AsyncIterableQueue, CancellablePromise, Future, gracefullyCancel } from '../utils.js';\n\nexport const proto = {};\n\nexport class PlayoutHandle extends EventEmitter {\n #audioSource: AudioSource;\n #sampleRate: number;\n #itemId: string;\n #contentIndex: number;\n /** @internal */\n synchronizer: TextAudioSynchronizer;\n /** @internal */\n doneFut: Future;\n /** @internal */\n intFut: Future;\n /** @internal */\n #interrupted: boolean;\n /** @internal */\n pushedDuration: number;\n /** @internal */\n totalPlayedTime: number | undefined; // Set when playout is done\n\n constructor(\n audioSource: AudioSource,\n sampleRate: number,\n itemId: string,\n contentIndex: number,\n synchronizer: TextAudioSynchronizer,\n ) {\n super();\n this.#audioSource = audioSource;\n this.#sampleRate = sampleRate;\n this.#itemId = itemId;\n this.#contentIndex = contentIndex;\n this.synchronizer = synchronizer;\n this.doneFut = new Future();\n this.intFut = new Future();\n this.#interrupted = false;\n this.pushedDuration = 0;\n this.totalPlayedTime = undefined;\n }\n\n get itemId(): string {\n return this.#itemId;\n }\n\n get audioSamples(): number {\n if (this.totalPlayedTime !== undefined) {\n return Math.floor(this.totalPlayedTime * this.#sampleRate);\n }\n\n return Math.floor(\n (this.pushedDuration - this.#audioSource.queuedDuration) * (this.#sampleRate / 1000),\n );\n }\n\n get textChars(): number {\n return this.synchronizer.playedText.length;\n }\n\n get contentIndex(): number {\n return this.#contentIndex;\n }\n\n get interrupted(): boolean {\n return this.#interrupted;\n }\n\n get done(): boolean {\n return this.doneFut.done || this.#interrupted;\n }\n\n interrupt() {\n if (this.doneFut.done) return;\n this.intFut.resolve();\n this.#interrupted = true;\n }\n}\n\nexport class AgentPlayout extends EventEmitter {\n #audioSource: AudioSource;\n #playoutTask: CancellablePromise<void> | null;\n #sampleRate: number;\n #numChannels: number;\n #inFrameSize: number;\n #outFrameSize: number;\n constructor(\n audioSource: AudioSource,\n sampleRate: number,\n numChannels: number,\n inFrameSize: number,\n outFrameSize: number,\n ) {\n super();\n this.#audioSource = audioSource;\n this.#playoutTask = null;\n this.#sampleRate = sampleRate;\n this.#numChannels = numChannels;\n this.#inFrameSize = inFrameSize;\n this.#outFrameSize = outFrameSize;\n }\n\n play(\n itemId: string,\n contentIndex: number,\n synchronizer: TextAudioSynchronizer,\n textStream: AsyncIterableQueue<string>,\n audioStream: AsyncIterableQueue<AudioFrame>,\n ): PlayoutHandle {\n const handle = new PlayoutHandle(\n this.#audioSource,\n this.#sampleRate,\n itemId,\n contentIndex,\n synchronizer,\n );\n this.#playoutTask = this.#makePlayoutTask(this.#playoutTask, handle, textStream, audioStream);\n return handle;\n }\n\n #makePlayoutTask(\n oldTask: CancellablePromise<void> | null,\n handle: PlayoutHandle,\n textStream: AsyncIterableQueue<string>,\n audioStream: AsyncIterableQueue<AudioFrame>,\n ): CancellablePromise<void> {\n return new CancellablePromise<void>((resolve, reject, onCancel) => {\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n (async () => {\n try {\n if (oldTask) {\n await gracefullyCancel(oldTask);\n }\n\n let firstFrame = true;\n\n const readText = () =>\n new CancellablePromise<void>((resolveText, rejectText, onCancelText) => {\n let cancelledText = false;\n onCancelText(() => {\n cancelledText = true;\n });\n\n (async () => {\n try {\n for await (const text of textStream) {\n if (cancelledText || cancelled) {\n break;\n }\n handle.synchronizer.pushText(text);\n }\n handle.synchronizer.markTextSegmentEnd();\n resolveText();\n } catch (error) {\n rejectText(error);\n }\n })();\n });\n\n const capture = () =>\n new CancellablePromise<void>((resolveCapture, rejectCapture, onCancelCapture) => {\n let cancelledCapture = false;\n onCancelCapture(() => {\n cancelledCapture = true;\n });\n\n (async () => {\n try {\n const samplesPerChannel = this.#outFrameSize;\n const bstream = new AudioByteStream(\n this.#sampleRate,\n this.#numChannels,\n samplesPerChannel,\n );\n\n for await (const frame of audioStream) {\n if (cancelledCapture || cancelled) {\n break;\n }\n if (firstFrame) {\n handle.synchronizer.segmentPlayoutStarted();\n this.emit('playout_started');\n firstFrame = false;\n }\n\n handle.synchronizer.pushAudio(frame);\n\n for (const f of bstream.write(frame.data.buffer)) {\n handle.pushedDuration += (f.samplesPerChannel / f.sampleRate) * 1000;\n await this.#audioSource.captureFrame(f);\n }\n }\n\n if (!cancelledCapture && !cancelled) {\n for (const f of bstream.flush()) {\n handle.pushedDuration += (f.samplesPerChannel / f.sampleRate) * 1000;\n await this.#audioSource.captureFrame(f);\n }\n\n handle.synchronizer.markAudioSegmentEnd();\n\n await this.#audioSource.waitForPlayout();\n }\n\n resolveCapture();\n } catch (error) {\n rejectCapture(error);\n }\n })();\n });\n\n const readTextTask = readText();\n const captureTask = capture();\n\n try {\n await Promise.race([captureTask, handle.intFut.await]);\n } finally {\n if (!captureTask.isCancelled) {\n await gracefullyCancel(captureTask);\n }\n\n handle.totalPlayedTime = handle.pushedDuration - this.#audioSource.queuedDuration;\n\n if (handle.interrupted || captureTask.error) {\n await handle.synchronizer.close(true);\n this.#audioSource.clearQueue(); // make sure to remove any queued frames\n }\n\n if (!readTextTask.isCancelled) {\n await gracefullyCancel(readTextTask);\n }\n\n if (!firstFrame) {\n this.emit('playout_stopped', handle.interrupted);\n }\n\n handle.doneFut.resolve();\n await handle.synchronizer.close(false);\n }\n\n resolve();\n } catch (error) {\n reject(error);\n }\n })();\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/multimodal/agent_playout.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { type AudioSource } from '@livekit/rtc-node';\nimport { EventEmitter } from 'node:events';\nimport { AudioByteStream } from '../audio.js';\nimport type { TextAudioSynchronizer } from '../transcription.js';\nimport { type AsyncIterableQueue, CancellablePromise, Future, gracefullyCancel } from '../utils.js';\n\nexport const proto = {};\n\nexport class PlayoutHandle extends EventEmitter {\n #audioSource: AudioSource;\n #sampleRate: number;\n #itemId: string;\n #contentIndex: number;\n /** @internal */\n synchronizer: TextAudioSynchronizer;\n /** @internal */\n doneFut: Future;\n /** @internal */\n intFut: Future;\n /** @internal */\n #interrupted: boolean;\n /** @internal */\n pushedDuration: number;\n /** @internal */\n totalPlayedTime: number | undefined; // Set when playout is done\n\n constructor(\n audioSource: AudioSource,\n sampleRate: number,\n itemId: string,\n contentIndex: number,\n synchronizer: TextAudioSynchronizer,\n ) {\n super();\n this.#audioSource = audioSource;\n this.#sampleRate = sampleRate;\n this.#itemId = itemId;\n this.#contentIndex = contentIndex;\n this.synchronizer = synchronizer;\n this.doneFut = new Future();\n this.intFut = new Future();\n this.#interrupted = false;\n this.pushedDuration = 0;\n this.totalPlayedTime = undefined;\n }\n\n get itemId(): string {\n return this.#itemId;\n }\n\n get audioSamples(): number {\n if (this.totalPlayedTime !== undefined) {\n return Math.floor(this.totalPlayedTime * this.#sampleRate);\n }\n\n return Math.max(\n 0,\n Math.floor(\n (this.pushedDuration - this.#audioSource.queuedDuration) * (this.#sampleRate / 1000),\n ),\n );\n }\n\n get textChars(): number {\n return this.synchronizer.playedText.length;\n }\n\n get contentIndex(): number {\n return this.#contentIndex;\n }\n\n get interrupted(): boolean {\n return this.#interrupted;\n }\n\n get done(): boolean {\n return this.doneFut.done || this.#interrupted;\n }\n\n interrupt() {\n if (this.doneFut.done) return;\n this.intFut.resolve();\n this.#interrupted = true;\n }\n}\n\nexport class AgentPlayout extends EventEmitter {\n #audioSource: AudioSource;\n #playoutTask: CancellablePromise<void> | null;\n #sampleRate: number;\n #numChannels: number;\n #inFrameSize: number;\n #outFrameSize: number;\n constructor(\n audioSource: AudioSource,\n sampleRate: number,\n numChannels: number,\n inFrameSize: number,\n outFrameSize: number,\n ) {\n super();\n this.#audioSource = audioSource;\n this.#playoutTask = null;\n this.#sampleRate = sampleRate;\n this.#numChannels = numChannels;\n this.#inFrameSize = inFrameSize;\n this.#outFrameSize = outFrameSize;\n }\n\n play(\n itemId: string,\n contentIndex: number,\n synchronizer: TextAudioSynchronizer,\n textStream: AsyncIterableQueue<string>,\n audioStream: AsyncIterableQueue<AudioFrame>,\n ): PlayoutHandle {\n const handle = new PlayoutHandle(\n this.#audioSource,\n this.#sampleRate,\n itemId,\n contentIndex,\n synchronizer,\n );\n this.#playoutTask = this.#makePlayoutTask(this.#playoutTask, handle, textStream, audioStream);\n return handle;\n }\n\n #makePlayoutTask(\n oldTask: CancellablePromise<void> | null,\n handle: PlayoutHandle,\n textStream: AsyncIterableQueue<string>,\n audioStream: AsyncIterableQueue<AudioFrame>,\n ): CancellablePromise<void> {\n return new CancellablePromise<void>((resolve, reject, onCancel) => {\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n (async () => {\n try {\n if (oldTask) {\n await gracefullyCancel(oldTask);\n }\n\n let firstFrame = true;\n\n const readText = () =>\n new CancellablePromise<void>((resolveText, rejectText, onCancelText) => {\n let cancelledText = false;\n onCancelText(() => {\n cancelledText = true;\n });\n\n (async () => {\n try {\n for await (const text of textStream) {\n if (cancelledText || cancelled) {\n break;\n }\n handle.synchronizer.pushText(text);\n }\n handle.synchronizer.markTextSegmentEnd();\n resolveText();\n } catch (error) {\n rejectText(error);\n }\n })();\n });\n\n const capture = () =>\n new CancellablePromise<void>((resolveCapture, rejectCapture, onCancelCapture) => {\n let cancelledCapture = false;\n onCancelCapture(() => {\n cancelledCapture = true;\n });\n\n (async () => {\n try {\n const samplesPerChannel = this.#outFrameSize;\n const bstream = new AudioByteStream(\n this.#sampleRate,\n this.#numChannels,\n samplesPerChannel,\n );\n\n for await (const frame of audioStream) {\n if (cancelledCapture || cancelled) {\n break;\n }\n if (firstFrame) {\n handle.synchronizer.segmentPlayoutStarted();\n this.emit('playout_started');\n firstFrame = false;\n }\n\n handle.synchronizer.pushAudio(frame);\n\n for (const f of bstream.write(frame.data.buffer)) {\n handle.pushedDuration += (f.samplesPerChannel / f.sampleRate) * 1000;\n await this.#audioSource.captureFrame(f);\n }\n }\n\n if (!cancelledCapture && !cancelled) {\n for (const f of bstream.flush()) {\n handle.pushedDuration += (f.samplesPerChannel / f.sampleRate) * 1000;\n await this.#audioSource.captureFrame(f);\n }\n\n handle.synchronizer.markAudioSegmentEnd();\n\n await this.#audioSource.waitForPlayout();\n }\n\n resolveCapture();\n } catch (error) {\n rejectCapture(error);\n }\n })();\n });\n\n const readTextTask = readText();\n const captureTask = capture();\n\n try {\n await Promise.race([captureTask, handle.intFut.await]);\n } finally {\n if (!captureTask.isCancelled) {\n await gracefullyCancel(captureTask);\n }\n\n handle.totalPlayedTime = handle.pushedDuration - this.#audioSource.queuedDuration;\n\n if (handle.interrupted || captureTask.error) {\n await handle.synchronizer.close(true);\n this.#audioSource.clearQueue(); // make sure to remove any queued frames\n }\n\n if (!readTextTask.isCancelled) {\n await gracefullyCancel(readTextTask);\n }\n\n if (!firstFrame) {\n this.emit('playout_stopped', handle.interrupted);\n }\n\n handle.doneFut.resolve();\n await handle.synchronizer.close(false);\n }\n\n resolve();\n } catch (error) {\n reject(error);\n }\n })();\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,yBAA6B;AAC7B,mBAAgC;AAEhC,mBAAsF;AAE/E,MAAM,QAAQ,CAAC;AAEf,MAAM,sBAAsB,gCAAa;AAAA,EAC9C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA,YACE,aACA,YACA,QACA,cACA,cACA;AACA,UAAM;AACN,SAAK,eAAe;AACpB,SAAK,cAAc;AACnB,SAAK,UAAU;AACf,SAAK,gBAAgB;AACrB,SAAK,eAAe;AACpB,SAAK,UAAU,IAAI,oBAAO;AAC1B,SAAK,SAAS,IAAI,oBAAO;AACzB,SAAK,eAAe;AACpB,SAAK,iBAAiB;AACtB,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,IAAI,SAAiB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,eAAuB;AACzB,QAAI,KAAK,oBAAoB,QAAW;AACtC,aAAO,KAAK,MAAM,KAAK,kBAAkB,KAAK,WAAW;AAAA,IAC3D;AAEA,WAAO,KAAK;AAAA,MACV;AAAA,MACA,KAAK;AAAA,SACF,KAAK,iBAAiB,KAAK,aAAa,mBAAmB,KAAK,cAAc;AAAA,MACjF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,IAAI,YAAoB;AACtB,WAAO,KAAK,aAAa,WAAW;AAAA,EACtC;AAAA,EAEA,IAAI,eAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,cAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAgB;AAClB,WAAO,KAAK,QAAQ,QAAQ,KAAK;AAAA,EACnC;AAAA,EAEA,YAAY;AACV,QAAI,KAAK,QAAQ,KAAM;AACvB,SAAK,OAAO,QAAQ;AACpB,SAAK,eAAe;AAAA,EACtB;AACF;AAEO,MAAM,qBAAqB,gCAAa;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YACE,aACA,YACA,aACA,aACA,cACA;AACA,UAAM;AACN,SAAK,eAAe;AACpB,SAAK,eAAe;AACpB,SAAK,cAAc;AACnB,SAAK,eAAe;AACpB,SAAK,eAAe;AACpB,SAAK,gBAAgB;AAAA,EACvB;AAAA,EAEA,KACE,QACA,cACA,cACA,YACA,aACe;AACf,UAAM,SAAS,IAAI;AAAA,MACjB,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,SAAK,eAAe,KAAK,iBAAiB,KAAK,cAAc,QAAQ,YAAY,WAAW;AAC5F,WAAO;AAAA,EACT;AAAA,EAEA,iBACE,SACA,QACA,YACA,aAC0B;AAC1B,WAAO,IAAI,gCAAyB,CAAC,SAAS,QAAQ,aAAa;AACjE,UAAI,YAAY;AAChB,eAAS,MAAM;AACb,oBAAY;AAAA,MACd,CAAC;AAED,OAAC,YAAY;AACX,YAAI;AACF,cAAI,SAAS;AACX,sBAAM,+BAAiB,OAAO;AAAA,UAChC;AAEA,cAAI,aAAa;AAEjB,gBAAM,WAAW,MACf,IAAI,gCAAyB,CAAC,aAAa,YAAY,iBAAiB;AACtE,gBAAI,gBAAgB;AACpB,yBAAa,MAAM;AACjB,8BAAgB;AAAA,YAClB,CAAC;AAED,aAAC,YAAY;AACX,kBAAI;AACF,iCAAiB,QAAQ,YAAY;AACnC,sBAAI,iBAAiB,WAAW;AAC9B;AAAA,kBACF;AACA,yBAAO,aAAa,SAAS,IAAI;AAAA,gBACnC;AACA,uBAAO,aAAa,mBAAmB;AACvC,4BAAY;AAAA,cACd,SAAS,OAAO;AACd,2BAAW,KAAK;AAAA,cAClB;AAAA,YACF,GAAG;AAAA,UACL,CAAC;AAEH,gBAAM,UAAU,MACd,IAAI,gCAAyB,CAAC,gBAAgB,eAAe,oBAAoB;AAC/E,gBAAI,mBAAmB;AACvB,4BAAgB,MAAM;AACpB,iCAAmB;AAAA,YACrB,CAAC;AAED,aAAC,YAAY;AACX,kBAAI;AACF,sBAAM,oBAAoB,KAAK;AAC/B,sBAAM,UAAU,IAAI;AAAA,kBAClB,KAAK;AAAA,kBACL,KAAK;AAAA,kBACL;AAAA,gBACF;AAEA,iCAAiB,SAAS,aAAa;AACrC,sBAAI,oBAAoB,WAAW;AACjC;AAAA,kBACF;AACA,sBAAI,YAAY;AACd,2BAAO,aAAa,sBAAsB;AAC1C,yBAAK,KAAK,iBAAiB;AAC3B,iCAAa;AAAA,kBACf;AAEA,yBAAO,aAAa,UAAU,KAAK;AAEnC,6BAAW,KAAK,QAAQ,MAAM,MAAM,KAAK,MAAM,GAAG;AAChD,2BAAO,kBAAmB,EAAE,oBAAoB,EAAE,aAAc;AAChE,0BAAM,KAAK,aAAa,aAAa,CAAC;AAAA,kBACxC;AAAA,gBACF;AAEA,oBAAI,CAAC,oBAAoB,CAAC,WAAW;AACnC,6BAAW,KAAK,QAAQ,MAAM,GAAG;AAC/B,2BAAO,kBAAmB,EAAE,oBAAoB,EAAE,aAAc;AAChE,0BAAM,KAAK,aAAa,aAAa,CAAC;AAAA,kBACxC;AAEA,yBAAO,aAAa,oBAAoB;AAExC,wBAAM,KAAK,aAAa,eAAe;AAAA,gBACzC;AAEA,+BAAe;AAAA,cACjB,SAAS,OAAO;AACd,8BAAc,KAAK;AAAA,cACrB;AAAA,YACF,GAAG;AAAA,UACL,CAAC;AAEH,gBAAM,eAAe,SAAS;AAC9B,gBAAM,cAAc,QAAQ;AAE5B,cAAI;AACF,kBAAM,QAAQ,KAAK,CAAC,aAAa,OAAO,OAAO,KAAK,CAAC;AAAA,UACvD,UAAE;AACA,gBAAI,CAAC,YAAY,aAAa;AAC5B,wBAAM,+BAAiB,WAAW;AAAA,YACpC;AAEA,mBAAO,kBAAkB,OAAO,iBAAiB,KAAK,aAAa;AAEnE,gBAAI,OAAO,eAAe,YAAY,OAAO;AAC3C,oBAAM,OAAO,aAAa,MAAM,IAAI;AACpC,mBAAK,aAAa,WAAW;AAAA,YAC/B;AAEA,gBAAI,CAAC,aAAa,aAAa;AAC7B,wBAAM,+BAAiB,YAAY;AAAA,YACrC;AAEA,gBAAI,CAAC,YAAY;AACf,mBAAK,KAAK,mBAAmB,OAAO,WAAW;AAAA,YACjD;AAEA,mBAAO,QAAQ,QAAQ;AACvB,kBAAM,OAAO,aAAa,MAAM,KAAK;AAAA,UACvC;AAEA,kBAAQ;AAAA,QACV,SAAS,OAAO;AACd,iBAAO,KAAK;AAAA,QACd;AAAA,MACF,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent_playout.d.ts","sourceRoot":"","sources":["../../src/multimodal/agent_playout.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,EAAoB,MAAM,aAAa,CAAC;AAEpG,eAAO,MAAM,KAAK,IAAK,CAAC;AAExB,qBAAa,aAAc,SAAQ,YAAY;;IAK7C,gBAAgB;IAChB,YAAY,EAAE,qBAAqB,CAAC;IACpC,gBAAgB;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB;IAChB,MAAM,EAAE,MAAM,CAAC;IAGf,gBAAgB;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB;IAChB,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;gBAGlC,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,qBAAqB;IAerC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,YAAY,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"agent_playout.d.ts","sourceRoot":"","sources":["../../src/multimodal/agent_playout.ts"],"names":[],"mappings":";AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,EAAoB,MAAM,aAAa,CAAC;AAEpG,eAAO,MAAM,KAAK,IAAK,CAAC;AAExB,qBAAa,aAAc,SAAQ,YAAY;;IAK7C,gBAAgB;IAChB,YAAY,EAAE,qBAAqB,CAAC;IACpC,gBAAgB;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB;IAChB,MAAM,EAAE,MAAM,CAAC;IAGf,gBAAgB;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB;IAChB,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;gBAGlC,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,qBAAqB;IAerC,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,YAAY,IAAI,MAAM,CAWzB;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,SAAS;CAKV;AAED,qBAAa,YAAa,SAAQ,YAAY;;gBAQ1C,WAAW,EAAE,WAAW,EACxB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM;IAWtB,IAAI,CACF,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,qBAAqB,EACnC,UAAU,EAAE,kBAAkB,CAAC,MAAM,CAAC,EACtC,WAAW,EAAE,kBAAkB,CAAC,UAAU,CAAC,GAC1C,aAAa;CA+IjB"}
|
|
@@ -40,8 +40,11 @@ class PlayoutHandle extends EventEmitter {
|
|
|
40
40
|
if (this.totalPlayedTime !== void 0) {
|
|
41
41
|
return Math.floor(this.totalPlayedTime * this.#sampleRate);
|
|
42
42
|
}
|
|
43
|
-
return Math.
|
|
44
|
-
|
|
43
|
+
return Math.max(
|
|
44
|
+
0,
|
|
45
|
+
Math.floor(
|
|
46
|
+
(this.pushedDuration - this.#audioSource.queuedDuration) * (this.#sampleRate / 1e3)
|
|
47
|
+
)
|
|
45
48
|
);
|
|
46
49
|
}
|
|
47
50
|
get textChars() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/multimodal/agent_playout.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { type AudioSource } from '@livekit/rtc-node';\nimport { EventEmitter } from 'node:events';\nimport { AudioByteStream } from '../audio.js';\nimport type { TextAudioSynchronizer } from '../transcription.js';\nimport { type AsyncIterableQueue, CancellablePromise, Future, gracefullyCancel } from '../utils.js';\n\nexport const proto = {};\n\nexport class PlayoutHandle extends EventEmitter {\n #audioSource: AudioSource;\n #sampleRate: number;\n #itemId: string;\n #contentIndex: number;\n /** @internal */\n synchronizer: TextAudioSynchronizer;\n /** @internal */\n doneFut: Future;\n /** @internal */\n intFut: Future;\n /** @internal */\n #interrupted: boolean;\n /** @internal */\n pushedDuration: number;\n /** @internal */\n totalPlayedTime: number | undefined; // Set when playout is done\n\n constructor(\n audioSource: AudioSource,\n sampleRate: number,\n itemId: string,\n contentIndex: number,\n synchronizer: TextAudioSynchronizer,\n ) {\n super();\n this.#audioSource = audioSource;\n this.#sampleRate = sampleRate;\n this.#itemId = itemId;\n this.#contentIndex = contentIndex;\n this.synchronizer = synchronizer;\n this.doneFut = new Future();\n this.intFut = new Future();\n this.#interrupted = false;\n this.pushedDuration = 0;\n this.totalPlayedTime = undefined;\n }\n\n get itemId(): string {\n return this.#itemId;\n }\n\n get audioSamples(): number {\n if (this.totalPlayedTime !== undefined) {\n return Math.floor(this.totalPlayedTime * this.#sampleRate);\n }\n\n return Math.floor(\n (this.pushedDuration - this.#audioSource.queuedDuration) * (this.#sampleRate / 1000),\n );\n }\n\n get textChars(): number {\n return this.synchronizer.playedText.length;\n }\n\n get contentIndex(): number {\n return this.#contentIndex;\n }\n\n get interrupted(): boolean {\n return this.#interrupted;\n }\n\n get done(): boolean {\n return this.doneFut.done || this.#interrupted;\n }\n\n interrupt() {\n if (this.doneFut.done) return;\n this.intFut.resolve();\n this.#interrupted = true;\n }\n}\n\nexport class AgentPlayout extends EventEmitter {\n #audioSource: AudioSource;\n #playoutTask: CancellablePromise<void> | null;\n #sampleRate: number;\n #numChannels: number;\n #inFrameSize: number;\n #outFrameSize: number;\n constructor(\n audioSource: AudioSource,\n sampleRate: number,\n numChannels: number,\n inFrameSize: number,\n outFrameSize: number,\n ) {\n super();\n this.#audioSource = audioSource;\n this.#playoutTask = null;\n this.#sampleRate = sampleRate;\n this.#numChannels = numChannels;\n this.#inFrameSize = inFrameSize;\n this.#outFrameSize = outFrameSize;\n }\n\n play(\n itemId: string,\n contentIndex: number,\n synchronizer: TextAudioSynchronizer,\n textStream: AsyncIterableQueue<string>,\n audioStream: AsyncIterableQueue<AudioFrame>,\n ): PlayoutHandle {\n const handle = new PlayoutHandle(\n this.#audioSource,\n this.#sampleRate,\n itemId,\n contentIndex,\n synchronizer,\n );\n this.#playoutTask = this.#makePlayoutTask(this.#playoutTask, handle, textStream, audioStream);\n return handle;\n }\n\n #makePlayoutTask(\n oldTask: CancellablePromise<void> | null,\n handle: PlayoutHandle,\n textStream: AsyncIterableQueue<string>,\n audioStream: AsyncIterableQueue<AudioFrame>,\n ): CancellablePromise<void> {\n return new CancellablePromise<void>((resolve, reject, onCancel) => {\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n (async () => {\n try {\n if (oldTask) {\n await gracefullyCancel(oldTask);\n }\n\n let firstFrame = true;\n\n const readText = () =>\n new CancellablePromise<void>((resolveText, rejectText, onCancelText) => {\n let cancelledText = false;\n onCancelText(() => {\n cancelledText = true;\n });\n\n (async () => {\n try {\n for await (const text of textStream) {\n if (cancelledText || cancelled) {\n break;\n }\n handle.synchronizer.pushText(text);\n }\n handle.synchronizer.markTextSegmentEnd();\n resolveText();\n } catch (error) {\n rejectText(error);\n }\n })();\n });\n\n const capture = () =>\n new CancellablePromise<void>((resolveCapture, rejectCapture, onCancelCapture) => {\n let cancelledCapture = false;\n onCancelCapture(() => {\n cancelledCapture = true;\n });\n\n (async () => {\n try {\n const samplesPerChannel = this.#outFrameSize;\n const bstream = new AudioByteStream(\n this.#sampleRate,\n this.#numChannels,\n samplesPerChannel,\n );\n\n for await (const frame of audioStream) {\n if (cancelledCapture || cancelled) {\n break;\n }\n if (firstFrame) {\n handle.synchronizer.segmentPlayoutStarted();\n this.emit('playout_started');\n firstFrame = false;\n }\n\n handle.synchronizer.pushAudio(frame);\n\n for (const f of bstream.write(frame.data.buffer)) {\n handle.pushedDuration += (f.samplesPerChannel / f.sampleRate) * 1000;\n await this.#audioSource.captureFrame(f);\n }\n }\n\n if (!cancelledCapture && !cancelled) {\n for (const f of bstream.flush()) {\n handle.pushedDuration += (f.samplesPerChannel / f.sampleRate) * 1000;\n await this.#audioSource.captureFrame(f);\n }\n\n handle.synchronizer.markAudioSegmentEnd();\n\n await this.#audioSource.waitForPlayout();\n }\n\n resolveCapture();\n } catch (error) {\n rejectCapture(error);\n }\n })();\n });\n\n const readTextTask = readText();\n const captureTask = capture();\n\n try {\n await Promise.race([captureTask, handle.intFut.await]);\n } finally {\n if (!captureTask.isCancelled) {\n await gracefullyCancel(captureTask);\n }\n\n handle.totalPlayedTime = handle.pushedDuration - this.#audioSource.queuedDuration;\n\n if (handle.interrupted || captureTask.error) {\n await handle.synchronizer.close(true);\n this.#audioSource.clearQueue(); // make sure to remove any queued frames\n }\n\n if (!readTextTask.isCancelled) {\n await gracefullyCancel(readTextTask);\n }\n\n if (!firstFrame) {\n this.emit('playout_stopped', handle.interrupted);\n }\n\n handle.doneFut.resolve();\n await handle.synchronizer.close(false);\n }\n\n resolve();\n } catch (error) {\n reject(error);\n }\n })();\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/multimodal/agent_playout.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { type AudioSource } from '@livekit/rtc-node';\nimport { EventEmitter } from 'node:events';\nimport { AudioByteStream } from '../audio.js';\nimport type { TextAudioSynchronizer } from '../transcription.js';\nimport { type AsyncIterableQueue, CancellablePromise, Future, gracefullyCancel } from '../utils.js';\n\nexport const proto = {};\n\nexport class PlayoutHandle extends EventEmitter {\n #audioSource: AudioSource;\n #sampleRate: number;\n #itemId: string;\n #contentIndex: number;\n /** @internal */\n synchronizer: TextAudioSynchronizer;\n /** @internal */\n doneFut: Future;\n /** @internal */\n intFut: Future;\n /** @internal */\n #interrupted: boolean;\n /** @internal */\n pushedDuration: number;\n /** @internal */\n totalPlayedTime: number | undefined; // Set when playout is done\n\n constructor(\n audioSource: AudioSource,\n sampleRate: number,\n itemId: string,\n contentIndex: number,\n synchronizer: TextAudioSynchronizer,\n ) {\n super();\n this.#audioSource = audioSource;\n this.#sampleRate = sampleRate;\n this.#itemId = itemId;\n this.#contentIndex = contentIndex;\n this.synchronizer = synchronizer;\n this.doneFut = new Future();\n this.intFut = new Future();\n this.#interrupted = false;\n this.pushedDuration = 0;\n this.totalPlayedTime = undefined;\n }\n\n get itemId(): string {\n return this.#itemId;\n }\n\n get audioSamples(): number {\n if (this.totalPlayedTime !== undefined) {\n return Math.floor(this.totalPlayedTime * this.#sampleRate);\n }\n\n return Math.max(\n 0,\n Math.floor(\n (this.pushedDuration - this.#audioSource.queuedDuration) * (this.#sampleRate / 1000),\n ),\n );\n }\n\n get textChars(): number {\n return this.synchronizer.playedText.length;\n }\n\n get contentIndex(): number {\n return this.#contentIndex;\n }\n\n get interrupted(): boolean {\n return this.#interrupted;\n }\n\n get done(): boolean {\n return this.doneFut.done || this.#interrupted;\n }\n\n interrupt() {\n if (this.doneFut.done) return;\n this.intFut.resolve();\n this.#interrupted = true;\n }\n}\n\nexport class AgentPlayout extends EventEmitter {\n #audioSource: AudioSource;\n #playoutTask: CancellablePromise<void> | null;\n #sampleRate: number;\n #numChannels: number;\n #inFrameSize: number;\n #outFrameSize: number;\n constructor(\n audioSource: AudioSource,\n sampleRate: number,\n numChannels: number,\n inFrameSize: number,\n outFrameSize: number,\n ) {\n super();\n this.#audioSource = audioSource;\n this.#playoutTask = null;\n this.#sampleRate = sampleRate;\n this.#numChannels = numChannels;\n this.#inFrameSize = inFrameSize;\n this.#outFrameSize = outFrameSize;\n }\n\n play(\n itemId: string,\n contentIndex: number,\n synchronizer: TextAudioSynchronizer,\n textStream: AsyncIterableQueue<string>,\n audioStream: AsyncIterableQueue<AudioFrame>,\n ): PlayoutHandle {\n const handle = new PlayoutHandle(\n this.#audioSource,\n this.#sampleRate,\n itemId,\n contentIndex,\n synchronizer,\n );\n this.#playoutTask = this.#makePlayoutTask(this.#playoutTask, handle, textStream, audioStream);\n return handle;\n }\n\n #makePlayoutTask(\n oldTask: CancellablePromise<void> | null,\n handle: PlayoutHandle,\n textStream: AsyncIterableQueue<string>,\n audioStream: AsyncIterableQueue<AudioFrame>,\n ): CancellablePromise<void> {\n return new CancellablePromise<void>((resolve, reject, onCancel) => {\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n (async () => {\n try {\n if (oldTask) {\n await gracefullyCancel(oldTask);\n }\n\n let firstFrame = true;\n\n const readText = () =>\n new CancellablePromise<void>((resolveText, rejectText, onCancelText) => {\n let cancelledText = false;\n onCancelText(() => {\n cancelledText = true;\n });\n\n (async () => {\n try {\n for await (const text of textStream) {\n if (cancelledText || cancelled) {\n break;\n }\n handle.synchronizer.pushText(text);\n }\n handle.synchronizer.markTextSegmentEnd();\n resolveText();\n } catch (error) {\n rejectText(error);\n }\n })();\n });\n\n const capture = () =>\n new CancellablePromise<void>((resolveCapture, rejectCapture, onCancelCapture) => {\n let cancelledCapture = false;\n onCancelCapture(() => {\n cancelledCapture = true;\n });\n\n (async () => {\n try {\n const samplesPerChannel = this.#outFrameSize;\n const bstream = new AudioByteStream(\n this.#sampleRate,\n this.#numChannels,\n samplesPerChannel,\n );\n\n for await (const frame of audioStream) {\n if (cancelledCapture || cancelled) {\n break;\n }\n if (firstFrame) {\n handle.synchronizer.segmentPlayoutStarted();\n this.emit('playout_started');\n firstFrame = false;\n }\n\n handle.synchronizer.pushAudio(frame);\n\n for (const f of bstream.write(frame.data.buffer)) {\n handle.pushedDuration += (f.samplesPerChannel / f.sampleRate) * 1000;\n await this.#audioSource.captureFrame(f);\n }\n }\n\n if (!cancelledCapture && !cancelled) {\n for (const f of bstream.flush()) {\n handle.pushedDuration += (f.samplesPerChannel / f.sampleRate) * 1000;\n await this.#audioSource.captureFrame(f);\n }\n\n handle.synchronizer.markAudioSegmentEnd();\n\n await this.#audioSource.waitForPlayout();\n }\n\n resolveCapture();\n } catch (error) {\n rejectCapture(error);\n }\n })();\n });\n\n const readTextTask = readText();\n const captureTask = capture();\n\n try {\n await Promise.race([captureTask, handle.intFut.await]);\n } finally {\n if (!captureTask.isCancelled) {\n await gracefullyCancel(captureTask);\n }\n\n handle.totalPlayedTime = handle.pushedDuration - this.#audioSource.queuedDuration;\n\n if (handle.interrupted || captureTask.error) {\n await handle.synchronizer.close(true);\n this.#audioSource.clearQueue(); // make sure to remove any queued frames\n }\n\n if (!readTextTask.isCancelled) {\n await gracefullyCancel(readTextTask);\n }\n\n if (!firstFrame) {\n this.emit('playout_stopped', handle.interrupted);\n }\n\n handle.doneFut.resolve();\n await handle.synchronizer.close(false);\n }\n\n resolve();\n } catch (error) {\n reject(error);\n }\n })();\n });\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -177,7 +177,9 @@ const streamSynthesisTask = (stream, handle) => {
|
|
|
177
177
|
handle.synchronizer.pushText(text);
|
|
178
178
|
ttsStream.pushText(text);
|
|
179
179
|
}
|
|
180
|
-
|
|
180
|
+
if (!cancelled) {
|
|
181
|
+
handle.synchronizer.markTextSegmentEnd();
|
|
182
|
+
}
|
|
181
183
|
if (!fullText || fullText.trim().length === 0) {
|
|
182
184
|
cancelled = true;
|
|
183
185
|
handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pipeline/agent_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { log } from '../log.js';\nimport type { TextAudioSynchronizer } from '../transcription.js';\nimport { SynthesizeStream, type TTS } from '../tts/index.js';\nimport { AsyncIterableQueue, CancellablePromise, Future, gracefullyCancel } from '../utils.js';\nimport type { AgentPlayout, PlayoutHandle } from './agent_playout.js';\n\nexport type SpeechSource = AsyncIterable<string> | string | Promise<string>;\n\nexport class SynthesisHandle {\n static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n\n #speechId: string;\n text?: string;\n ttsSource: SpeechSource;\n #agentPlayout: AgentPlayout;\n tts: TTS;\n queue = new AsyncIterableQueue<AudioFrame | typeof SynthesisHandle.FLUSH_SENTINEL>();\n #playHandle?: PlayoutHandle;\n intFut = new Future();\n #logger = log();\n synchronizer: TextAudioSynchronizer;\n\n constructor(\n speechId: string,\n ttsSource: SpeechSource,\n agentPlayout: AgentPlayout,\n tts: TTS,\n synchronizer: TextAudioSynchronizer,\n ) {\n this.#speechId = speechId;\n this.ttsSource = ttsSource;\n this.#agentPlayout = agentPlayout;\n this.tts = tts;\n this.synchronizer = synchronizer;\n }\n\n get speechId(): string {\n return this.#speechId;\n }\n\n get validated(): boolean {\n return !!this.#playHandle;\n }\n\n get interrupted(): boolean {\n return this.intFut.done;\n }\n\n get playHandle(): PlayoutHandle | undefined {\n return this.#playHandle;\n }\n\n /** Validate the speech for playout. */\n play(): PlayoutHandle {\n if (this.interrupted) {\n throw new Error('synthesis was interrupted');\n }\n\n this.#playHandle = this.#agentPlayout.play(this.#speechId, this.queue, this.synchronizer);\n return this.#playHandle;\n }\n\n /** Interrupt the speech. */\n interrupt() {\n if (this.interrupted) {\n return;\n }\n\n this.#logger.child({ speechId: this.#speechId }).debug('interrupting synthesis/playout');\n this.#playHandle?.interrupt();\n this.intFut.resolve();\n }\n}\n\nexport class AgentOutput {\n #agentPlayout: AgentPlayout;\n #tts: TTS;\n #tasks: CancellablePromise<void>[] = [];\n\n constructor(agentPlayout: AgentPlayout, tts: TTS) {\n this.#agentPlayout = agentPlayout;\n this.#tts = tts;\n }\n\n get playout(): AgentPlayout {\n return this.#agentPlayout;\n }\n\n async close() {\n this.#tasks.forEach((task) => task.cancel());\n await Promise.all(this.#tasks);\n }\n\n synthesize(\n speechId: string,\n ttsSource: SpeechSource,\n synchronizer: TextAudioSynchronizer,\n ): SynthesisHandle {\n const handle = new SynthesisHandle(\n speechId,\n ttsSource,\n this.#agentPlayout,\n this.#tts,\n synchronizer,\n );\n const task = this.#synthesize(handle);\n this.#tasks.push(task);\n task.finally(() => this.#tasks.splice(this.#tasks.indexOf(task)));\n return handle;\n }\n\n #synthesize(handle: SynthesisHandle): CancellablePromise<void> {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n const ttsSource = await handle.ttsSource;\n let task: CancellablePromise<string>;\n if (typeof ttsSource === 'string') {\n task = stringSynthesisTask(ttsSource, handle);\n } else {\n task = streamSynthesisTask(ttsSource, handle);\n }\n\n onCancel(() => {\n gracefullyCancel(task);\n });\n\n try {\n await Promise.any([task, handle.intFut.await]);\n } finally {\n if (handle.intFut.done) {\n gracefullyCancel(task);\n } else {\n task.then((text) => {\n handle.text = text;\n });\n }\n }\n\n resolve();\n });\n }\n}\n\nconst stringSynthesisTask = (text: string, handle: SynthesisHandle): CancellablePromise<string> => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n const ttsStream = handle.tts.stream();\n ttsStream.pushText(text);\n handle.synchronizer.pushText(text);\n handle.synchronizer.markTextSegmentEnd();\n ttsStream.flush();\n ttsStream.endInput();\n for await (const audio of ttsStream) {\n if (cancelled || audio === SynthesizeStream.END_OF_STREAM) {\n break;\n }\n handle.queue.put(audio.frame);\n }\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n\n resolve(text);\n });\n};\n\nconst streamSynthesisTask = (\n stream: AsyncIterable<string>,\n handle: SynthesisHandle,\n): CancellablePromise<string> => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n let fullText = '';\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n const ttsStream = handle.tts.stream();\n const readGeneratedAudio = async () => {\n for await (const audio of ttsStream) {\n if (cancelled) break;\n if (audio === SynthesizeStream.END_OF_STREAM) {\n break;\n }\n handle.queue.put(audio.frame);\n }\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n };\n readGeneratedAudio();\n\n for await (const text of stream) {\n fullText += text;\n if (cancelled) break;\n handle.synchronizer.pushText(text);\n ttsStream.pushText(text);\n }\n handle.synchronizer.markTextSegmentEnd();\n\n // end the audio queue early if there is no actual text to turn into speech\n if (!fullText || fullText.trim().length === 0) {\n cancelled = true;\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n }\n ttsStream.flush();\n ttsStream.endInput();\n\n resolve(fullText);\n });\n};\n"],"mappings":"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
|
|
1
|
+
{"version":3,"sources":["../../src/pipeline/agent_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { log } from '../log.js';\nimport type { TextAudioSynchronizer } from '../transcription.js';\nimport { SynthesizeStream, type TTS } from '../tts/index.js';\nimport { AsyncIterableQueue, CancellablePromise, Future, gracefullyCancel } from '../utils.js';\nimport type { AgentPlayout, PlayoutHandle } from './agent_playout.js';\n\nexport type SpeechSource = AsyncIterable<string> | string | Promise<string>;\n\nexport class SynthesisHandle {\n static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n\n #speechId: string;\n text?: string;\n ttsSource: SpeechSource;\n #agentPlayout: AgentPlayout;\n tts: TTS;\n queue = new AsyncIterableQueue<AudioFrame | typeof SynthesisHandle.FLUSH_SENTINEL>();\n #playHandle?: PlayoutHandle;\n intFut = new Future();\n #logger = log();\n synchronizer: TextAudioSynchronizer;\n\n constructor(\n speechId: string,\n ttsSource: SpeechSource,\n agentPlayout: AgentPlayout,\n tts: TTS,\n synchronizer: TextAudioSynchronizer,\n ) {\n this.#speechId = speechId;\n this.ttsSource = ttsSource;\n this.#agentPlayout = agentPlayout;\n this.tts = tts;\n this.synchronizer = synchronizer;\n }\n\n get speechId(): string {\n return this.#speechId;\n }\n\n get validated(): boolean {\n return !!this.#playHandle;\n }\n\n get interrupted(): boolean {\n return this.intFut.done;\n }\n\n get playHandle(): PlayoutHandle | undefined {\n return this.#playHandle;\n }\n\n /** Validate the speech for playout. */\n play(): PlayoutHandle {\n if (this.interrupted) {\n throw new Error('synthesis was interrupted');\n }\n\n this.#playHandle = this.#agentPlayout.play(this.#speechId, this.queue, this.synchronizer);\n return this.#playHandle;\n }\n\n /** Interrupt the speech. */\n interrupt() {\n if (this.interrupted) {\n return;\n }\n\n this.#logger.child({ speechId: this.#speechId }).debug('interrupting synthesis/playout');\n this.#playHandle?.interrupt();\n this.intFut.resolve();\n }\n}\n\nexport class AgentOutput {\n #agentPlayout: AgentPlayout;\n #tts: TTS;\n #tasks: CancellablePromise<void>[] = [];\n\n constructor(agentPlayout: AgentPlayout, tts: TTS) {\n this.#agentPlayout = agentPlayout;\n this.#tts = tts;\n }\n\n get playout(): AgentPlayout {\n return this.#agentPlayout;\n }\n\n async close() {\n this.#tasks.forEach((task) => task.cancel());\n await Promise.all(this.#tasks);\n }\n\n synthesize(\n speechId: string,\n ttsSource: SpeechSource,\n synchronizer: TextAudioSynchronizer,\n ): SynthesisHandle {\n const handle = new SynthesisHandle(\n speechId,\n ttsSource,\n this.#agentPlayout,\n this.#tts,\n synchronizer,\n );\n const task = this.#synthesize(handle);\n this.#tasks.push(task);\n task.finally(() => this.#tasks.splice(this.#tasks.indexOf(task)));\n return handle;\n }\n\n #synthesize(handle: SynthesisHandle): CancellablePromise<void> {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n const ttsSource = await handle.ttsSource;\n let task: CancellablePromise<string>;\n if (typeof ttsSource === 'string') {\n task = stringSynthesisTask(ttsSource, handle);\n } else {\n task = streamSynthesisTask(ttsSource, handle);\n }\n\n onCancel(() => {\n gracefullyCancel(task);\n });\n\n try {\n await Promise.any([task, handle.intFut.await]);\n } finally {\n if (handle.intFut.done) {\n gracefullyCancel(task);\n } else {\n task.then((text) => {\n handle.text = text;\n });\n }\n }\n\n resolve();\n });\n }\n}\n\nconst stringSynthesisTask = (text: string, handle: SynthesisHandle): CancellablePromise<string> => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n const ttsStream = handle.tts.stream();\n ttsStream.pushText(text);\n handle.synchronizer.pushText(text);\n handle.synchronizer.markTextSegmentEnd();\n ttsStream.flush();\n ttsStream.endInput();\n for await (const audio of ttsStream) {\n if (cancelled || audio === SynthesizeStream.END_OF_STREAM) {\n break;\n }\n handle.queue.put(audio.frame);\n }\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n\n resolve(text);\n });\n};\n\nconst streamSynthesisTask = (\n stream: AsyncIterable<string>,\n handle: SynthesisHandle,\n): CancellablePromise<string> => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n let fullText = '';\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n const ttsStream = handle.tts.stream();\n const readGeneratedAudio = async () => {\n for await (const audio of ttsStream) {\n if (cancelled) break;\n if (audio === SynthesizeStream.END_OF_STREAM) {\n break;\n }\n handle.queue.put(audio.frame);\n }\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n };\n readGeneratedAudio();\n\n for await (const text of stream) {\n fullText += text;\n if (cancelled) break;\n handle.synchronizer.pushText(text);\n ttsStream.pushText(text);\n }\n if (!cancelled) {\n handle.synchronizer.markTextSegmentEnd();\n }\n\n // end the audio queue early if there is no actual text to turn into speech\n if (!fullText || fullText.trim().length === 0) {\n cancelled = true;\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n }\n ttsStream.flush();\n ttsStream.endInput();\n\n resolve(fullText);\n });\n};\n"],"mappings":"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","names":[]}
|
|
@@ -153,7 +153,9 @@ const streamSynthesisTask = (stream, handle) => {
|
|
|
153
153
|
handle.synchronizer.pushText(text);
|
|
154
154
|
ttsStream.pushText(text);
|
|
155
155
|
}
|
|
156
|
-
|
|
156
|
+
if (!cancelled) {
|
|
157
|
+
handle.synchronizer.markTextSegmentEnd();
|
|
158
|
+
}
|
|
157
159
|
if (!fullText || fullText.trim().length === 0) {
|
|
158
160
|
cancelled = true;
|
|
159
161
|
handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/pipeline/agent_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { log } from '../log.js';\nimport type { TextAudioSynchronizer } from '../transcription.js';\nimport { SynthesizeStream, type TTS } from '../tts/index.js';\nimport { AsyncIterableQueue, CancellablePromise, Future, gracefullyCancel } from '../utils.js';\nimport type { AgentPlayout, PlayoutHandle } from './agent_playout.js';\n\nexport type SpeechSource = AsyncIterable<string> | string | Promise<string>;\n\nexport class SynthesisHandle {\n static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n\n #speechId: string;\n text?: string;\n ttsSource: SpeechSource;\n #agentPlayout: AgentPlayout;\n tts: TTS;\n queue = new AsyncIterableQueue<AudioFrame | typeof SynthesisHandle.FLUSH_SENTINEL>();\n #playHandle?: PlayoutHandle;\n intFut = new Future();\n #logger = log();\n synchronizer: TextAudioSynchronizer;\n\n constructor(\n speechId: string,\n ttsSource: SpeechSource,\n agentPlayout: AgentPlayout,\n tts: TTS,\n synchronizer: TextAudioSynchronizer,\n ) {\n this.#speechId = speechId;\n this.ttsSource = ttsSource;\n this.#agentPlayout = agentPlayout;\n this.tts = tts;\n this.synchronizer = synchronizer;\n }\n\n get speechId(): string {\n return this.#speechId;\n }\n\n get validated(): boolean {\n return !!this.#playHandle;\n }\n\n get interrupted(): boolean {\n return this.intFut.done;\n }\n\n get playHandle(): PlayoutHandle | undefined {\n return this.#playHandle;\n }\n\n /** Validate the speech for playout. */\n play(): PlayoutHandle {\n if (this.interrupted) {\n throw new Error('synthesis was interrupted');\n }\n\n this.#playHandle = this.#agentPlayout.play(this.#speechId, this.queue, this.synchronizer);\n return this.#playHandle;\n }\n\n /** Interrupt the speech. */\n interrupt() {\n if (this.interrupted) {\n return;\n }\n\n this.#logger.child({ speechId: this.#speechId }).debug('interrupting synthesis/playout');\n this.#playHandle?.interrupt();\n this.intFut.resolve();\n }\n}\n\nexport class AgentOutput {\n #agentPlayout: AgentPlayout;\n #tts: TTS;\n #tasks: CancellablePromise<void>[] = [];\n\n constructor(agentPlayout: AgentPlayout, tts: TTS) {\n this.#agentPlayout = agentPlayout;\n this.#tts = tts;\n }\n\n get playout(): AgentPlayout {\n return this.#agentPlayout;\n }\n\n async close() {\n this.#tasks.forEach((task) => task.cancel());\n await Promise.all(this.#tasks);\n }\n\n synthesize(\n speechId: string,\n ttsSource: SpeechSource,\n synchronizer: TextAudioSynchronizer,\n ): SynthesisHandle {\n const handle = new SynthesisHandle(\n speechId,\n ttsSource,\n this.#agentPlayout,\n this.#tts,\n synchronizer,\n );\n const task = this.#synthesize(handle);\n this.#tasks.push(task);\n task.finally(() => this.#tasks.splice(this.#tasks.indexOf(task)));\n return handle;\n }\n\n #synthesize(handle: SynthesisHandle): CancellablePromise<void> {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n const ttsSource = await handle.ttsSource;\n let task: CancellablePromise<string>;\n if (typeof ttsSource === 'string') {\n task = stringSynthesisTask(ttsSource, handle);\n } else {\n task = streamSynthesisTask(ttsSource, handle);\n }\n\n onCancel(() => {\n gracefullyCancel(task);\n });\n\n try {\n await Promise.any([task, handle.intFut.await]);\n } finally {\n if (handle.intFut.done) {\n gracefullyCancel(task);\n } else {\n task.then((text) => {\n handle.text = text;\n });\n }\n }\n\n resolve();\n });\n }\n}\n\nconst stringSynthesisTask = (text: string, handle: SynthesisHandle): CancellablePromise<string> => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n const ttsStream = handle.tts.stream();\n ttsStream.pushText(text);\n handle.synchronizer.pushText(text);\n handle.synchronizer.markTextSegmentEnd();\n ttsStream.flush();\n ttsStream.endInput();\n for await (const audio of ttsStream) {\n if (cancelled || audio === SynthesizeStream.END_OF_STREAM) {\n break;\n }\n handle.queue.put(audio.frame);\n }\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n\n resolve(text);\n });\n};\n\nconst streamSynthesisTask = (\n stream: AsyncIterable<string>,\n handle: SynthesisHandle,\n): CancellablePromise<string> => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n let fullText = '';\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n const ttsStream = handle.tts.stream();\n const readGeneratedAudio = async () => {\n for await (const audio of ttsStream) {\n if (cancelled) break;\n if (audio === SynthesizeStream.END_OF_STREAM) {\n break;\n }\n handle.queue.put(audio.frame);\n }\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n };\n readGeneratedAudio();\n\n for await (const text of stream) {\n fullText += text;\n if (cancelled) break;\n handle.synchronizer.pushText(text);\n ttsStream.pushText(text);\n }\n handle.synchronizer.markTextSegmentEnd();\n\n // end the audio queue early if there is no actual text to turn into speech\n if (!fullText || fullText.trim().length === 0) {\n cancelled = true;\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n }\n ttsStream.flush();\n ttsStream.endInput();\n\n resolve(fullText);\n });\n};\n"],"mappings":"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
|
|
1
|
+
{"version":3,"sources":["../../src/pipeline/agent_output.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { log } from '../log.js';\nimport type { TextAudioSynchronizer } from '../transcription.js';\nimport { SynthesizeStream, type TTS } from '../tts/index.js';\nimport { AsyncIterableQueue, CancellablePromise, Future, gracefullyCancel } from '../utils.js';\nimport type { AgentPlayout, PlayoutHandle } from './agent_playout.js';\n\nexport type SpeechSource = AsyncIterable<string> | string | Promise<string>;\n\nexport class SynthesisHandle {\n static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n\n #speechId: string;\n text?: string;\n ttsSource: SpeechSource;\n #agentPlayout: AgentPlayout;\n tts: TTS;\n queue = new AsyncIterableQueue<AudioFrame | typeof SynthesisHandle.FLUSH_SENTINEL>();\n #playHandle?: PlayoutHandle;\n intFut = new Future();\n #logger = log();\n synchronizer: TextAudioSynchronizer;\n\n constructor(\n speechId: string,\n ttsSource: SpeechSource,\n agentPlayout: AgentPlayout,\n tts: TTS,\n synchronizer: TextAudioSynchronizer,\n ) {\n this.#speechId = speechId;\n this.ttsSource = ttsSource;\n this.#agentPlayout = agentPlayout;\n this.tts = tts;\n this.synchronizer = synchronizer;\n }\n\n get speechId(): string {\n return this.#speechId;\n }\n\n get validated(): boolean {\n return !!this.#playHandle;\n }\n\n get interrupted(): boolean {\n return this.intFut.done;\n }\n\n get playHandle(): PlayoutHandle | undefined {\n return this.#playHandle;\n }\n\n /** Validate the speech for playout. */\n play(): PlayoutHandle {\n if (this.interrupted) {\n throw new Error('synthesis was interrupted');\n }\n\n this.#playHandle = this.#agentPlayout.play(this.#speechId, this.queue, this.synchronizer);\n return this.#playHandle;\n }\n\n /** Interrupt the speech. */\n interrupt() {\n if (this.interrupted) {\n return;\n }\n\n this.#logger.child({ speechId: this.#speechId }).debug('interrupting synthesis/playout');\n this.#playHandle?.interrupt();\n this.intFut.resolve();\n }\n}\n\nexport class AgentOutput {\n #agentPlayout: AgentPlayout;\n #tts: TTS;\n #tasks: CancellablePromise<void>[] = [];\n\n constructor(agentPlayout: AgentPlayout, tts: TTS) {\n this.#agentPlayout = agentPlayout;\n this.#tts = tts;\n }\n\n get playout(): AgentPlayout {\n return this.#agentPlayout;\n }\n\n async close() {\n this.#tasks.forEach((task) => task.cancel());\n await Promise.all(this.#tasks);\n }\n\n synthesize(\n speechId: string,\n ttsSource: SpeechSource,\n synchronizer: TextAudioSynchronizer,\n ): SynthesisHandle {\n const handle = new SynthesisHandle(\n speechId,\n ttsSource,\n this.#agentPlayout,\n this.#tts,\n synchronizer,\n );\n const task = this.#synthesize(handle);\n this.#tasks.push(task);\n task.finally(() => this.#tasks.splice(this.#tasks.indexOf(task)));\n return handle;\n }\n\n #synthesize(handle: SynthesisHandle): CancellablePromise<void> {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n const ttsSource = await handle.ttsSource;\n let task: CancellablePromise<string>;\n if (typeof ttsSource === 'string') {\n task = stringSynthesisTask(ttsSource, handle);\n } else {\n task = streamSynthesisTask(ttsSource, handle);\n }\n\n onCancel(() => {\n gracefullyCancel(task);\n });\n\n try {\n await Promise.any([task, handle.intFut.await]);\n } finally {\n if (handle.intFut.done) {\n gracefullyCancel(task);\n } else {\n task.then((text) => {\n handle.text = text;\n });\n }\n }\n\n resolve();\n });\n }\n}\n\nconst stringSynthesisTask = (text: string, handle: SynthesisHandle): CancellablePromise<string> => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n const ttsStream = handle.tts.stream();\n ttsStream.pushText(text);\n handle.synchronizer.pushText(text);\n handle.synchronizer.markTextSegmentEnd();\n ttsStream.flush();\n ttsStream.endInput();\n for await (const audio of ttsStream) {\n if (cancelled || audio === SynthesizeStream.END_OF_STREAM) {\n break;\n }\n handle.queue.put(audio.frame);\n }\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n\n resolve(text);\n });\n};\n\nconst streamSynthesisTask = (\n stream: AsyncIterable<string>,\n handle: SynthesisHandle,\n): CancellablePromise<string> => {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n return new CancellablePromise(async (resolve, _, onCancel) => {\n let fullText = '';\n let cancelled = false;\n onCancel(() => {\n cancelled = true;\n });\n\n const ttsStream = handle.tts.stream();\n const readGeneratedAudio = async () => {\n for await (const audio of ttsStream) {\n if (cancelled) break;\n if (audio === SynthesizeStream.END_OF_STREAM) {\n break;\n }\n handle.queue.put(audio.frame);\n }\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n };\n readGeneratedAudio();\n\n for await (const text of stream) {\n fullText += text;\n if (cancelled) break;\n handle.synchronizer.pushText(text);\n ttsStream.pushText(text);\n }\n if (!cancelled) {\n handle.synchronizer.markTextSegmentEnd();\n }\n\n // end the audio queue early if there is no actual text to turn into speech\n if (!fullText || fullText.trim().length === 0) {\n cancelled = true;\n handle.queue.put(SynthesisHandle.FLUSH_SENTINEL);\n }\n ttsStream.flush();\n ttsStream.endInput();\n\n resolve(fullText);\n });\n};\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -45,6 +45,15 @@ const runWorker = async (args: CliArgs) => {
|
|
|
45
45
|
process.exit(130); // SIGINT exit code
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
process.once('SIGTERM', async () => {
|
|
49
|
+
if (args.production) {
|
|
50
|
+
await worker.drain();
|
|
51
|
+
}
|
|
52
|
+
await worker.close();
|
|
53
|
+
logger.info('worker closed');
|
|
54
|
+
process.exit(143); // SIGTERM exit code
|
|
55
|
+
});
|
|
56
|
+
|
|
48
57
|
try {
|
|
49
58
|
await worker.run();
|
|
50
59
|
} catch {
|
|
@@ -14,6 +14,10 @@ const ORPHANED_TIMEOUT = 15 * 1000;
|
|
|
14
14
|
// this is handled in cli, triggering a termination of all child processes at once.
|
|
15
15
|
process.on('SIGINT', () => {});
|
|
16
16
|
|
|
17
|
+
// don't do anything on SIGTERM
|
|
18
|
+
// Render uses SIGTERM in autoscale, this ensures the processes are properly drained if needed
|
|
19
|
+
process.on('SIGTERM', () => {});
|
|
20
|
+
|
|
17
21
|
await once(process, 'message').then(([msg]: IPCMessage[]) => {
|
|
18
22
|
msg = msg!;
|
|
19
23
|
if (msg.case !== 'initializeRequest') {
|
|
@@ -140,6 +140,10 @@ const startJob = (
|
|
|
140
140
|
// this is handled in cli, triggering a termination of all child processes at once.
|
|
141
141
|
process.on('SIGINT', () => {});
|
|
142
142
|
|
|
143
|
+
// don't do anything on SIGTERM
|
|
144
|
+
// Render uses SIGTERM in autoscale, this ensures the processes are properly drained if needed
|
|
145
|
+
process.on('SIGTERM', () => {});
|
|
146
|
+
|
|
143
147
|
await once(process, 'message').then(([msg]: IPCMessage[]) => {
|
|
144
148
|
msg = msg!;
|
|
145
149
|
if (msg.case !== 'initializeRequest') {
|
|
@@ -150,6 +154,10 @@ const startJob = (
|
|
|
150
154
|
const proc = new JobProcess();
|
|
151
155
|
let logger = log().child({ pid: proc.pid });
|
|
152
156
|
|
|
157
|
+
process.on('unhandledRejection', (reason) => {
|
|
158
|
+
logger.error(reason);
|
|
159
|
+
});
|
|
160
|
+
|
|
153
161
|
logger.debug('initializing job runner');
|
|
154
162
|
agent.prewarm(proc);
|
|
155
163
|
logger.debug('job runner initialized');
|
|
@@ -57,8 +57,11 @@ export class PlayoutHandle extends EventEmitter {
|
|
|
57
57
|
return Math.floor(this.totalPlayedTime * this.#sampleRate);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
return Math.
|
|
61
|
-
|
|
60
|
+
return Math.max(
|
|
61
|
+
0,
|
|
62
|
+
Math.floor(
|
|
63
|
+
(this.pushedDuration - this.#audioSource.queuedDuration) * (this.#sampleRate / 1000),
|
|
64
|
+
),
|
|
62
65
|
);
|
|
63
66
|
}
|
|
64
67
|
|
|
@@ -202,7 +202,9 @@ const streamSynthesisTask = (
|
|
|
202
202
|
handle.synchronizer.pushText(text);
|
|
203
203
|
ttsStream.pushText(text);
|
|
204
204
|
}
|
|
205
|
-
|
|
205
|
+
if (!cancelled) {
|
|
206
|
+
handle.synchronizer.markTextSegmentEnd();
|
|
207
|
+
}
|
|
206
208
|
|
|
207
209
|
// end the audio queue early if there is no actual text to turn into speech
|
|
208
210
|
if (!fullText || fullText.trim().length === 0) {
|