@livekit/agents 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_exceptions.cjs.map +1 -1
- package/dist/_exceptions.d.ts.map +1 -1
- package/dist/_exceptions.js.map +1 -1
- package/dist/audio.cjs +10 -0
- package/dist/audio.cjs.map +1 -1
- package/dist/audio.d.cts +1 -1
- package/dist/audio.d.ts +1 -1
- package/dist/audio.d.ts.map +1 -1
- package/dist/audio.js +10 -0
- package/dist/audio.js.map +1 -1
- package/dist/beta/workflows/task_group.cjs +7 -4
- package/dist/beta/workflows/task_group.cjs.map +1 -1
- package/dist/beta/workflows/task_group.d.ts.map +1 -1
- package/dist/beta/workflows/task_group.js +7 -4
- package/dist/beta/workflows/task_group.js.map +1 -1
- package/dist/inference/api_protos.d.cts +26 -26
- package/dist/inference/api_protos.d.ts +26 -26
- package/dist/inference/interruption/http_transport.cjs.map +1 -1
- package/dist/inference/interruption/http_transport.d.cts +3 -1
- package/dist/inference/interruption/http_transport.d.ts +3 -1
- package/dist/inference/interruption/http_transport.d.ts.map +1 -1
- package/dist/inference/interruption/http_transport.js.map +1 -1
- package/dist/inference/interruption/ws_transport.cjs +37 -32
- package/dist/inference/interruption/ws_transport.cjs.map +1 -1
- package/dist/inference/interruption/ws_transport.d.ts.map +1 -1
- package/dist/inference/interruption/ws_transport.js +37 -32
- package/dist/inference/interruption/ws_transport.js.map +1 -1
- package/dist/inference/tts.cjs +14 -1
- package/dist/inference/tts.cjs.map +1 -1
- package/dist/inference/tts.d.cts +42 -4
- package/dist/inference/tts.d.ts +42 -4
- package/dist/inference/tts.d.ts.map +1 -1
- package/dist/inference/tts.js +24 -3
- package/dist/inference/tts.js.map +1 -1
- package/dist/inference/tts.test.cjs +72 -0
- package/dist/inference/tts.test.cjs.map +1 -1
- package/dist/inference/tts.test.js +72 -0
- package/dist/inference/tts.test.js.map +1 -1
- package/dist/ipc/job_proc_lazy_main.cjs +7 -2
- package/dist/ipc/job_proc_lazy_main.cjs.map +1 -1
- package/dist/ipc/job_proc_lazy_main.js +7 -2
- package/dist/ipc/job_proc_lazy_main.js.map +1 -1
- package/dist/ipc/supervised_proc.cjs +4 -1
- package/dist/ipc/supervised_proc.cjs.map +1 -1
- package/dist/ipc/supervised_proc.d.ts.map +1 -1
- package/dist/ipc/supervised_proc.js +4 -1
- package/dist/ipc/supervised_proc.js.map +1 -1
- package/dist/ipc/supervised_proc.test.cjs +82 -0
- package/dist/ipc/supervised_proc.test.cjs.map +1 -1
- package/dist/ipc/supervised_proc.test.js +82 -0
- package/dist/ipc/supervised_proc.test.js.map +1 -1
- package/dist/job.cjs +2 -1
- package/dist/job.cjs.map +1 -1
- package/dist/job.d.ts.map +1 -1
- package/dist/job.js +2 -1
- package/dist/job.js.map +1 -1
- package/dist/llm/chat_context.cjs +102 -31
- package/dist/llm/chat_context.cjs.map +1 -1
- package/dist/llm/chat_context.d.ts.map +1 -1
- package/dist/llm/chat_context.js +102 -31
- package/dist/llm/chat_context.js.map +1 -1
- package/dist/llm/chat_context.test.cjs +123 -5
- package/dist/llm/chat_context.test.cjs.map +1 -1
- package/dist/llm/chat_context.test.js +123 -5
- package/dist/llm/chat_context.test.js.map +1 -1
- package/dist/llm/fallback_adapter.cjs +2 -0
- package/dist/llm/fallback_adapter.cjs.map +1 -1
- package/dist/llm/fallback_adapter.d.ts.map +1 -1
- package/dist/llm/fallback_adapter.js +2 -0
- package/dist/llm/fallback_adapter.js.map +1 -1
- package/dist/llm/index.cjs +2 -0
- package/dist/llm/index.cjs.map +1 -1
- package/dist/llm/index.d.cts +1 -1
- package/dist/llm/index.d.ts +1 -1
- package/dist/llm/index.d.ts.map +1 -1
- package/dist/llm/index.js +2 -0
- package/dist/llm/index.js.map +1 -1
- package/dist/llm/utils.cjs +89 -0
- package/dist/llm/utils.cjs.map +1 -1
- package/dist/llm/utils.d.cts +8 -0
- package/dist/llm/utils.d.ts +8 -0
- package/dist/llm/utils.d.ts.map +1 -1
- package/dist/llm/utils.js +88 -0
- package/dist/llm/utils.js.map +1 -1
- package/dist/llm/utils.test.cjs +90 -0
- package/dist/llm/utils.test.cjs.map +1 -1
- package/dist/llm/utils.test.js +98 -2
- package/dist/llm/utils.test.js.map +1 -1
- package/dist/stt/stt.cjs +8 -0
- package/dist/stt/stt.cjs.map +1 -1
- package/dist/stt/stt.d.cts +8 -0
- package/dist/stt/stt.d.ts +8 -0
- package/dist/stt/stt.d.ts.map +1 -1
- package/dist/stt/stt.js +8 -0
- package/dist/stt/stt.js.map +1 -1
- package/dist/tts/fallback_adapter.cjs +6 -0
- package/dist/tts/fallback_adapter.cjs.map +1 -1
- package/dist/tts/fallback_adapter.d.ts.map +1 -1
- package/dist/tts/fallback_adapter.js +6 -0
- package/dist/tts/fallback_adapter.js.map +1 -1
- package/dist/typed_promise.cjs +48 -0
- package/dist/typed_promise.cjs.map +1 -0
- package/dist/typed_promise.d.cts +24 -0
- package/dist/typed_promise.d.ts +24 -0
- package/dist/typed_promise.d.ts.map +1 -0
- package/dist/typed_promise.js +28 -0
- package/dist/typed_promise.js.map +1 -0
- package/dist/utils.cjs +30 -2
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +18 -0
- package/dist/utils.d.ts +18 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +27 -2
- package/dist/utils.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/dist/voice/agent_activity.cjs +10 -0
- package/dist/voice/agent_activity.cjs.map +1 -1
- package/dist/voice/agent_activity.d.ts.map +1 -1
- package/dist/voice/agent_activity.js +11 -0
- package/dist/voice/agent_activity.js.map +1 -1
- package/dist/voice/agent_session.cjs +1 -1
- package/dist/voice/agent_session.cjs.map +1 -1
- package/dist/voice/agent_session.d.cts +4 -2
- package/dist/voice/agent_session.d.ts +4 -2
- package/dist/voice/agent_session.d.ts.map +1 -1
- package/dist/voice/agent_session.js +1 -1
- package/dist/voice/agent_session.js.map +1 -1
- package/dist/voice/events.cjs +11 -0
- package/dist/voice/events.cjs.map +1 -1
- package/dist/voice/events.d.cts +12 -1
- package/dist/voice/events.d.ts +12 -1
- package/dist/voice/events.d.ts.map +1 -1
- package/dist/voice/events.js +10 -0
- package/dist/voice/events.js.map +1 -1
- package/dist/voice/generation.cjs +23 -4
- package/dist/voice/generation.cjs.map +1 -1
- package/dist/voice/generation.d.ts.map +1 -1
- package/dist/voice/generation.js +32 -5
- package/dist/voice/generation.js.map +1 -1
- package/dist/voice/generation_tts_timeout.test.cjs +85 -0
- package/dist/voice/generation_tts_timeout.test.cjs.map +1 -0
- package/dist/voice/generation_tts_timeout.test.js +84 -0
- package/dist/voice/generation_tts_timeout.test.js.map +1 -0
- package/dist/voice/index.cjs.map +1 -1
- package/dist/voice/index.d.cts +1 -1
- package/dist/voice/index.d.ts +1 -1
- package/dist/voice/index.d.ts.map +1 -1
- package/dist/voice/index.js +3 -1
- package/dist/voice/index.js.map +1 -1
- package/dist/voice/recorder_io/recorder_io.cjs +1 -2
- package/dist/voice/recorder_io/recorder_io.cjs.map +1 -1
- package/dist/voice/recorder_io/recorder_io.d.ts.map +1 -1
- package/dist/voice/recorder_io/recorder_io.js +2 -3
- package/dist/voice/recorder_io/recorder_io.js.map +1 -1
- package/dist/voice/report.cjs +1 -1
- package/dist/voice/report.cjs.map +1 -1
- package/dist/voice/report.js +1 -1
- package/dist/voice/report.js.map +1 -1
- package/dist/voice/report.test.cjs +70 -0
- package/dist/voice/report.test.cjs.map +1 -1
- package/dist/voice/report.test.js +70 -0
- package/dist/voice/report.test.js.map +1 -1
- package/dist/voice/room_io/room_io.cjs +5 -1
- package/dist/voice/room_io/room_io.cjs.map +1 -1
- package/dist/voice/room_io/room_io.d.ts.map +1 -1
- package/dist/voice/room_io/room_io.js +5 -1
- package/dist/voice/room_io/room_io.js.map +1 -1
- package/dist/voice/room_io/room_io.test.cjs +18 -0
- package/dist/voice/room_io/room_io.test.cjs.map +1 -0
- package/dist/voice/room_io/room_io.test.js +17 -0
- package/dist/voice/room_io/room_io.test.js.map +1 -0
- package/package.json +4 -2
- package/src/_exceptions.ts +5 -0
- package/src/audio.ts +12 -1
- package/src/beta/workflows/task_group.ts +14 -5
- package/src/inference/interruption/http_transport.ts +2 -1
- package/src/inference/interruption/ws_transport.ts +44 -34
- package/src/inference/tts.test.ts +87 -0
- package/src/inference/tts.ts +71 -9
- package/src/ipc/job_proc_lazy_main.ts +7 -2
- package/src/ipc/supervised_proc.test.ts +96 -0
- package/src/ipc/supervised_proc.ts +8 -1
- package/src/job.ts +1 -0
- package/src/llm/chat_context.test.ts +137 -5
- package/src/llm/chat_context.ts +119 -38
- package/src/llm/fallback_adapter.ts +5 -2
- package/src/llm/index.ts +2 -0
- package/src/llm/utils.test.ts +103 -2
- package/src/llm/utils.ts +128 -0
- package/src/stt/stt.ts +9 -1
- package/src/tts/fallback_adapter.ts +9 -2
- package/src/typed_promise.ts +67 -0
- package/src/utils.ts +45 -2
- package/src/voice/agent_activity.ts +11 -0
- package/src/voice/agent_session.ts +13 -7
- package/src/voice/events.ts +21 -0
- package/src/voice/generation.ts +35 -8
- package/src/voice/generation_tts_timeout.test.ts +112 -0
- package/src/voice/index.ts +6 -1
- package/src/voice/recorder_io/recorder_io.ts +2 -7
- package/src/voice/report.test.ts +78 -0
- package/src/voice/report.ts +1 -1
- package/src/voice/room_io/room_io.test.ts +38 -0
- package/src/voice/room_io/room_io.ts +7 -2
|
@@ -119,6 +119,88 @@ async function getChildMemoryUsageMB(pid) {
|
|
|
119
119
|
(0, import_vitest.expect)(skipped).toBeGreaterThan(0);
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
|
+
(0, import_vitest.describe)("init timeout rejection handling", () => {
|
|
123
|
+
(0, import_vitest.it)("does not produce unhandled rejection when init times out", async () => {
|
|
124
|
+
var _a;
|
|
125
|
+
const unhandled = [];
|
|
126
|
+
const handler = (reason) => unhandled.push(reason);
|
|
127
|
+
process.on("unhandledRejection", handler);
|
|
128
|
+
const slowScript = (0, import_node_path.join)((0, import_node_os.tmpdir)(), "test_slow_init_child.mjs");
|
|
129
|
+
(0, import_node_fs.writeFileSync)(
|
|
130
|
+
slowScript,
|
|
131
|
+
`process.on('message', () => {
|
|
132
|
+
setTimeout(() => process.send({ case: 'initializeResponse' }), 200);
|
|
133
|
+
});
|
|
134
|
+
setInterval(() => {}, 1000);`
|
|
135
|
+
);
|
|
136
|
+
const { SupervisedProc } = await import("./supervised_proc.cjs");
|
|
137
|
+
class TestProc extends SupervisedProc {
|
|
138
|
+
createProcess() {
|
|
139
|
+
return (0, import_node_child_process.fork)(slowScript, [], { stdio: ["pipe", "pipe", "pipe", "ipc"] });
|
|
140
|
+
}
|
|
141
|
+
async mainTask() {
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const proc = new TestProc(
|
|
145
|
+
50,
|
|
146
|
+
// initializeTimeout — fires before child responds at 200ms
|
|
147
|
+
1e3,
|
|
148
|
+
// closeTimeout
|
|
149
|
+
0,
|
|
150
|
+
// memoryWarnMB
|
|
151
|
+
0,
|
|
152
|
+
// memoryLimitMB
|
|
153
|
+
5e3,
|
|
154
|
+
// pingInterval
|
|
155
|
+
6e4,
|
|
156
|
+
// pingTimeout
|
|
157
|
+
2500
|
|
158
|
+
// highPingThreshold
|
|
159
|
+
);
|
|
160
|
+
await proc.start();
|
|
161
|
+
await proc.initialize();
|
|
162
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
163
|
+
process.off("unhandledRejection", handler);
|
|
164
|
+
(_a = proc.proc) == null ? void 0 : _a.kill();
|
|
165
|
+
try {
|
|
166
|
+
(0, import_node_fs.unlinkSync)(slowScript);
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
(0, import_vitest.expect)(unhandled).toEqual([]);
|
|
170
|
+
});
|
|
171
|
+
(0, import_vitest.it)("join() resolves after init timeout instead of hanging forever", async () => {
|
|
172
|
+
var _a;
|
|
173
|
+
const slowScript = (0, import_node_path.join)((0, import_node_os.tmpdir)(), "test_slow_init_child_join.mjs");
|
|
174
|
+
(0, import_node_fs.writeFileSync)(
|
|
175
|
+
slowScript,
|
|
176
|
+
`process.on('message', () => {
|
|
177
|
+
setTimeout(() => process.send({ case: 'initializeResponse' }), 200);
|
|
178
|
+
});
|
|
179
|
+
setInterval(() => {}, 1000);`
|
|
180
|
+
);
|
|
181
|
+
const { SupervisedProc } = await import("./supervised_proc.cjs");
|
|
182
|
+
class TestProc extends SupervisedProc {
|
|
183
|
+
createProcess() {
|
|
184
|
+
return (0, import_node_child_process.fork)(slowScript, [], { stdio: ["pipe", "pipe", "pipe", "ipc"] });
|
|
185
|
+
}
|
|
186
|
+
async mainTask() {
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const proc = new TestProc(50, 1e3, 0, 0, 5e3, 6e4, 2500);
|
|
190
|
+
await proc.start();
|
|
191
|
+
await proc.initialize();
|
|
192
|
+
const result = await Promise.race([
|
|
193
|
+
proc.join().then(() => "resolved"),
|
|
194
|
+
new Promise((r) => setTimeout(() => r("timeout"), 2e3))
|
|
195
|
+
]);
|
|
196
|
+
(_a = proc.proc) == null ? void 0 : _a.kill();
|
|
197
|
+
try {
|
|
198
|
+
(0, import_node_fs.unlinkSync)(slowScript);
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
(0, import_vitest.expect)(result).toBe("resolved");
|
|
202
|
+
});
|
|
203
|
+
});
|
|
122
204
|
(0, import_vitest.describe)("timer cleanup", () => {
|
|
123
205
|
(0, import_vitest.it)("clearInterval stops the interval", async () => {
|
|
124
206
|
let count = 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ipc/supervised_proc.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { fork, spawn } from 'node:child_process';\nimport { unlinkSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport pidusage from 'pidusage';\nimport { afterAll, beforeAll, describe, expect, it } from 'vitest';\n\nconst childScript = join(tmpdir(), 'test_child.mjs');\n\nbeforeAll(() => {\n writeFileSync(\n childScript,\n `process.on('message', (msg) => process.send?.({ echo: msg }));\n setInterval(() => {}, 1000);`,\n );\n});\n\nafterAll(() => {\n try {\n unlinkSync(childScript);\n } catch {}\n});\n\nasync function getChildMemoryUsageMB(pid: number | undefined): Promise<number> {\n if (!pid) return 0;\n try {\n const stats = await pidusage(pid);\n return stats.memory / (1024 * 1024);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ESRCH') {\n return 0;\n }\n throw err;\n }\n}\n\ndescribe('pidusage on dead process', () => {\n it('raw pidusage throws on dead pid', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n\n child.kill('SIGKILL');\n await new Promise<void>((r) => child.on('exit', r));\n\n await expect(pidusage(pid)).rejects.toThrow();\n });\n\n it('fixed version returns 0 instead of crashing', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n\n child.kill('SIGKILL');\n await new Promise<void>((r) => child.on('exit', r));\n\n const mem = await getChildMemoryUsageMB(pid);\n expect(mem).toBe(0);\n });\n\n it('handles concurrent calls on dying process', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n child.kill('SIGKILL');\n\n const results = await Promise.all([\n getChildMemoryUsageMB(pid),\n getChildMemoryUsageMB(pid),\n getChildMemoryUsageMB(pid),\n ]);\n\n await exitPromise;\n expect(results.every((r) => r === 0)).toBe(true);\n });\n});\n\ndescribe('IPC send on dead process', () => {\n it('child.connected becomes false when child dies', async () => {\n const child = fork(childScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n await new Promise((r) => setTimeout(r, 50));\n expect(child.connected).toBe(true);\n\n child.kill('SIGKILL');\n await exitPromise;\n\n expect(child.connected).toBe(false);\n });\n\n it('checking connected before send prevents crash', async () => {\n const child = fork(childScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n // Suppress EPIPE errors that can occur due to race conditions between\n // child.connected check and the actual pipe state\n child.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code !== 'EPIPE') throw err;\n });\n\n let sent = 0;\n let skipped = 0;\n\n const interval = setInterval(() => {\n if (child.connected) {\n child.send({ ping: Date.now() });\n sent++;\n } else {\n skipped++;\n }\n }, 20);\n\n await new Promise((r) => setTimeout(r, 60));\n child.kill('SIGKILL');\n await exitPromise;\n await new Promise((r) => setTimeout(r, 80));\n clearInterval(interval);\n\n expect(sent).toBeGreaterThan(0);\n expect(skipped).toBeGreaterThan(0);\n });\n});\n\ndescribe('timer cleanup', () => {\n it('clearInterval stops the interval', async () => {\n let count = 0;\n const interval = setInterval(() => count++, 30);\n\n await new Promise((r) => setTimeout(r, 80));\n const countAtClear = count;\n clearInterval(interval);\n\n await new Promise((r) => setTimeout(r, 80));\n expect(count).toBe(countAtClear);\n });\n\n it('double clear is safe', () => {\n const interval = setInterval(() => {}, 100);\n const timeout = setTimeout(() => {}, 1000);\n\n clearInterval(interval);\n clearTimeout(timeout);\n\n expect(() => {\n clearInterval(interval);\n clearTimeout(timeout);\n }).not.toThrow();\n });\n});\n"],"mappings":"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","names":["pidusage"]}
|
|
1
|
+
{"version":3,"sources":["../../src/ipc/supervised_proc.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { fork, spawn } from 'node:child_process';\nimport { unlinkSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport pidusage from 'pidusage';\nimport { afterAll, beforeAll, describe, expect, it } from 'vitest';\n\nconst childScript = join(tmpdir(), 'test_child.mjs');\n\nbeforeAll(() => {\n writeFileSync(\n childScript,\n `process.on('message', (msg) => process.send?.({ echo: msg }));\n setInterval(() => {}, 1000);`,\n );\n});\n\nafterAll(() => {\n try {\n unlinkSync(childScript);\n } catch {}\n});\n\nasync function getChildMemoryUsageMB(pid: number | undefined): Promise<number> {\n if (!pid) return 0;\n try {\n const stats = await pidusage(pid);\n return stats.memory / (1024 * 1024);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ESRCH') {\n return 0;\n }\n throw err;\n }\n}\n\ndescribe('pidusage on dead process', () => {\n it('raw pidusage throws on dead pid', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n\n child.kill('SIGKILL');\n await new Promise<void>((r) => child.on('exit', r));\n\n await expect(pidusage(pid)).rejects.toThrow();\n });\n\n it('fixed version returns 0 instead of crashing', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n\n child.kill('SIGKILL');\n await new Promise<void>((r) => child.on('exit', r));\n\n const mem = await getChildMemoryUsageMB(pid);\n expect(mem).toBe(0);\n });\n\n it('handles concurrent calls on dying process', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n child.kill('SIGKILL');\n\n const results = await Promise.all([\n getChildMemoryUsageMB(pid),\n getChildMemoryUsageMB(pid),\n getChildMemoryUsageMB(pid),\n ]);\n\n await exitPromise;\n expect(results.every((r) => r === 0)).toBe(true);\n });\n});\n\ndescribe('IPC send on dead process', () => {\n it('child.connected becomes false when child dies', async () => {\n const child = fork(childScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n await new Promise((r) => setTimeout(r, 50));\n expect(child.connected).toBe(true);\n\n child.kill('SIGKILL');\n await exitPromise;\n\n expect(child.connected).toBe(false);\n });\n\n it('checking connected before send prevents crash', async () => {\n const child = fork(childScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n // Suppress EPIPE errors that can occur due to race conditions between\n // child.connected check and the actual pipe state\n child.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code !== 'EPIPE') throw err;\n });\n\n let sent = 0;\n let skipped = 0;\n\n const interval = setInterval(() => {\n if (child.connected) {\n child.send({ ping: Date.now() });\n sent++;\n } else {\n skipped++;\n }\n }, 20);\n\n await new Promise((r) => setTimeout(r, 60));\n child.kill('SIGKILL');\n await exitPromise;\n await new Promise((r) => setTimeout(r, 80));\n clearInterval(interval);\n\n expect(sent).toBeGreaterThan(0);\n expect(skipped).toBeGreaterThan(0);\n });\n});\n\ndescribe('init timeout rejection handling', () => {\n it('does not produce unhandled rejection when init times out', async () => {\n // Regression test: before the fix, run() was called without await in start().\n // When init timed out, the rejection in run()'s `await this.init.await` escaped\n // as an unhandled rejection — crashing the Node.js process.\n const unhandled: unknown[] = [];\n const handler = (reason: unknown) => unhandled.push(reason);\n process.on('unhandledRejection', handler);\n\n // Child that responds AFTER the timeout — simulates slow init under CPU pressure.\n // Timeout fires at 50ms (init.reject), child responds at 200ms (once() resolves).\n // Before the fix, init.reject caused an unhandled rejection in run().\n const slowScript = join(tmpdir(), 'test_slow_init_child.mjs');\n writeFileSync(\n slowScript,\n `process.on('message', () => {\n setTimeout(() => process.send({ case: 'initializeResponse' }), 200);\n });\n setInterval(() => {}, 1000);`,\n );\n\n const { SupervisedProc } = await import('./supervised_proc.js');\n class TestProc extends SupervisedProc {\n createProcess() {\n return fork(slowScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n }\n async mainTask() {}\n }\n\n const proc = new TestProc(\n 50, // initializeTimeout — fires before child responds at 200ms\n 1000, // closeTimeout\n 0, // memoryWarnMB\n 0, // memoryLimitMB\n 5000, // pingInterval\n 60000, // pingTimeout\n 2500, // highPingThreshold\n );\n\n await proc.start();\n // initialize() returns normally: child responds at 200ms, once() resolves,\n // but init was already rejected at 50ms — run() gets the rejection.\n await proc.initialize();\n\n // Give the event loop a tick for any unhandled rejection to surface\n await new Promise((r) => setTimeout(r, 100));\n\n process.off('unhandledRejection', handler);\n proc.proc?.kill();\n try {\n unlinkSync(slowScript);\n } catch {}\n\n expect(unhandled).toEqual([]);\n });\n\n it('join() resolves after init timeout instead of hanging forever', async () => {\n // When run() fails early (before registering proc event handlers),\n // #join must still resolve so that join() and close() don't hang.\n const slowScript = join(tmpdir(), 'test_slow_init_child_join.mjs');\n writeFileSync(\n slowScript,\n `process.on('message', () => {\n setTimeout(() => process.send({ case: 'initializeResponse' }), 200);\n });\n setInterval(() => {}, 1000);`,\n );\n\n const { SupervisedProc } = await import('./supervised_proc.js');\n class TestProc extends SupervisedProc {\n createProcess() {\n return fork(slowScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n }\n async mainTask() {}\n }\n\n const proc = new TestProc(50, 1000, 0, 0, 5000, 60000, 2500);\n\n await proc.start();\n await proc.initialize();\n\n // join() must resolve within a reasonable time, not hang forever\n const result = await Promise.race([\n proc.join().then(() => 'resolved'),\n new Promise((r) => setTimeout(() => r('timeout'), 2000)),\n ]);\n\n proc.proc?.kill();\n try {\n unlinkSync(slowScript);\n } catch {}\n\n expect(result).toBe('resolved');\n });\n});\n\ndescribe('timer cleanup', () => {\n it('clearInterval stops the interval', async () => {\n let count = 0;\n const interval = setInterval(() => count++, 30);\n\n await new Promise((r) => setTimeout(r, 80));\n const countAtClear = count;\n clearInterval(interval);\n\n await new Promise((r) => setTimeout(r, 80));\n expect(count).toBe(countAtClear);\n });\n\n it('double clear is safe', () => {\n const interval = setInterval(() => {}, 100);\n const timeout = setTimeout(() => {}, 1000);\n\n clearInterval(interval);\n clearTimeout(timeout);\n\n expect(() => {\n clearInterval(interval);\n clearTimeout(timeout);\n }).not.toThrow();\n });\n});\n"],"mappings":"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","names":["pidusage"]}
|
|
@@ -96,6 +96,88 @@ describe("IPC send on dead process", () => {
|
|
|
96
96
|
expect(skipped).toBeGreaterThan(0);
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
|
+
describe("init timeout rejection handling", () => {
|
|
100
|
+
it("does not produce unhandled rejection when init times out", async () => {
|
|
101
|
+
var _a;
|
|
102
|
+
const unhandled = [];
|
|
103
|
+
const handler = (reason) => unhandled.push(reason);
|
|
104
|
+
process.on("unhandledRejection", handler);
|
|
105
|
+
const slowScript = join(tmpdir(), "test_slow_init_child.mjs");
|
|
106
|
+
writeFileSync(
|
|
107
|
+
slowScript,
|
|
108
|
+
`process.on('message', () => {
|
|
109
|
+
setTimeout(() => process.send({ case: 'initializeResponse' }), 200);
|
|
110
|
+
});
|
|
111
|
+
setInterval(() => {}, 1000);`
|
|
112
|
+
);
|
|
113
|
+
const { SupervisedProc } = await import("./supervised_proc.js");
|
|
114
|
+
class TestProc extends SupervisedProc {
|
|
115
|
+
createProcess() {
|
|
116
|
+
return fork(slowScript, [], { stdio: ["pipe", "pipe", "pipe", "ipc"] });
|
|
117
|
+
}
|
|
118
|
+
async mainTask() {
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const proc = new TestProc(
|
|
122
|
+
50,
|
|
123
|
+
// initializeTimeout — fires before child responds at 200ms
|
|
124
|
+
1e3,
|
|
125
|
+
// closeTimeout
|
|
126
|
+
0,
|
|
127
|
+
// memoryWarnMB
|
|
128
|
+
0,
|
|
129
|
+
// memoryLimitMB
|
|
130
|
+
5e3,
|
|
131
|
+
// pingInterval
|
|
132
|
+
6e4,
|
|
133
|
+
// pingTimeout
|
|
134
|
+
2500
|
|
135
|
+
// highPingThreshold
|
|
136
|
+
);
|
|
137
|
+
await proc.start();
|
|
138
|
+
await proc.initialize();
|
|
139
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
140
|
+
process.off("unhandledRejection", handler);
|
|
141
|
+
(_a = proc.proc) == null ? void 0 : _a.kill();
|
|
142
|
+
try {
|
|
143
|
+
unlinkSync(slowScript);
|
|
144
|
+
} catch {
|
|
145
|
+
}
|
|
146
|
+
expect(unhandled).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
it("join() resolves after init timeout instead of hanging forever", async () => {
|
|
149
|
+
var _a;
|
|
150
|
+
const slowScript = join(tmpdir(), "test_slow_init_child_join.mjs");
|
|
151
|
+
writeFileSync(
|
|
152
|
+
slowScript,
|
|
153
|
+
`process.on('message', () => {
|
|
154
|
+
setTimeout(() => process.send({ case: 'initializeResponse' }), 200);
|
|
155
|
+
});
|
|
156
|
+
setInterval(() => {}, 1000);`
|
|
157
|
+
);
|
|
158
|
+
const { SupervisedProc } = await import("./supervised_proc.js");
|
|
159
|
+
class TestProc extends SupervisedProc {
|
|
160
|
+
createProcess() {
|
|
161
|
+
return fork(slowScript, [], { stdio: ["pipe", "pipe", "pipe", "ipc"] });
|
|
162
|
+
}
|
|
163
|
+
async mainTask() {
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const proc = new TestProc(50, 1e3, 0, 0, 5e3, 6e4, 2500);
|
|
167
|
+
await proc.start();
|
|
168
|
+
await proc.initialize();
|
|
169
|
+
const result = await Promise.race([
|
|
170
|
+
proc.join().then(() => "resolved"),
|
|
171
|
+
new Promise((r) => setTimeout(() => r("timeout"), 2e3))
|
|
172
|
+
]);
|
|
173
|
+
(_a = proc.proc) == null ? void 0 : _a.kill();
|
|
174
|
+
try {
|
|
175
|
+
unlinkSync(slowScript);
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
expect(result).toBe("resolved");
|
|
179
|
+
});
|
|
180
|
+
});
|
|
99
181
|
describe("timer cleanup", () => {
|
|
100
182
|
it("clearInterval stops the interval", async () => {
|
|
101
183
|
let count = 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/ipc/supervised_proc.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { fork, spawn } from 'node:child_process';\nimport { unlinkSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport pidusage from 'pidusage';\nimport { afterAll, beforeAll, describe, expect, it } from 'vitest';\n\nconst childScript = join(tmpdir(), 'test_child.mjs');\n\nbeforeAll(() => {\n writeFileSync(\n childScript,\n `process.on('message', (msg) => process.send?.({ echo: msg }));\n setInterval(() => {}, 1000);`,\n );\n});\n\nafterAll(() => {\n try {\n unlinkSync(childScript);\n } catch {}\n});\n\nasync function getChildMemoryUsageMB(pid: number | undefined): Promise<number> {\n if (!pid) return 0;\n try {\n const stats = await pidusage(pid);\n return stats.memory / (1024 * 1024);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ESRCH') {\n return 0;\n }\n throw err;\n }\n}\n\ndescribe('pidusage on dead process', () => {\n it('raw pidusage throws on dead pid', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n\n child.kill('SIGKILL');\n await new Promise<void>((r) => child.on('exit', r));\n\n await expect(pidusage(pid)).rejects.toThrow();\n });\n\n it('fixed version returns 0 instead of crashing', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n\n child.kill('SIGKILL');\n await new Promise<void>((r) => child.on('exit', r));\n\n const mem = await getChildMemoryUsageMB(pid);\n expect(mem).toBe(0);\n });\n\n it('handles concurrent calls on dying process', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n child.kill('SIGKILL');\n\n const results = await Promise.all([\n getChildMemoryUsageMB(pid),\n getChildMemoryUsageMB(pid),\n getChildMemoryUsageMB(pid),\n ]);\n\n await exitPromise;\n expect(results.every((r) => r === 0)).toBe(true);\n });\n});\n\ndescribe('IPC send on dead process', () => {\n it('child.connected becomes false when child dies', async () => {\n const child = fork(childScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n await new Promise((r) => setTimeout(r, 50));\n expect(child.connected).toBe(true);\n\n child.kill('SIGKILL');\n await exitPromise;\n\n expect(child.connected).toBe(false);\n });\n\n it('checking connected before send prevents crash', async () => {\n const child = fork(childScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n // Suppress EPIPE errors that can occur due to race conditions between\n // child.connected check and the actual pipe state\n child.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code !== 'EPIPE') throw err;\n });\n\n let sent = 0;\n let skipped = 0;\n\n const interval = setInterval(() => {\n if (child.connected) {\n child.send({ ping: Date.now() });\n sent++;\n } else {\n skipped++;\n }\n }, 20);\n\n await new Promise((r) => setTimeout(r, 60));\n child.kill('SIGKILL');\n await exitPromise;\n await new Promise((r) => setTimeout(r, 80));\n clearInterval(interval);\n\n expect(sent).toBeGreaterThan(0);\n expect(skipped).toBeGreaterThan(0);\n });\n});\n\ndescribe('timer cleanup', () => {\n it('clearInterval stops the interval', async () => {\n let count = 0;\n const interval = setInterval(() => count++, 30);\n\n await new Promise((r) => setTimeout(r, 80));\n const countAtClear = count;\n clearInterval(interval);\n\n await new Promise((r) => setTimeout(r, 80));\n expect(count).toBe(countAtClear);\n });\n\n it('double clear is safe', () => {\n const interval = setInterval(() => {}, 100);\n const timeout = setTimeout(() => {}, 1000);\n\n clearInterval(interval);\n clearTimeout(timeout);\n\n expect(() => {\n clearInterval(interval);\n clearTimeout(timeout);\n }).not.toThrow();\n });\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/ipc/supervised_proc.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { fork, spawn } from 'node:child_process';\nimport { unlinkSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport pidusage from 'pidusage';\nimport { afterAll, beforeAll, describe, expect, it } from 'vitest';\n\nconst childScript = join(tmpdir(), 'test_child.mjs');\n\nbeforeAll(() => {\n writeFileSync(\n childScript,\n `process.on('message', (msg) => process.send?.({ echo: msg }));\n setInterval(() => {}, 1000);`,\n );\n});\n\nafterAll(() => {\n try {\n unlinkSync(childScript);\n } catch {}\n});\n\nasync function getChildMemoryUsageMB(pid: number | undefined): Promise<number> {\n if (!pid) return 0;\n try {\n const stats = await pidusage(pid);\n return stats.memory / (1024 * 1024);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT' || code === 'ESRCH') {\n return 0;\n }\n throw err;\n }\n}\n\ndescribe('pidusage on dead process', () => {\n it('raw pidusage throws on dead pid', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n\n child.kill('SIGKILL');\n await new Promise<void>((r) => child.on('exit', r));\n\n await expect(pidusage(pid)).rejects.toThrow();\n });\n\n it('fixed version returns 0 instead of crashing', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n\n child.kill('SIGKILL');\n await new Promise<void>((r) => child.on('exit', r));\n\n const mem = await getChildMemoryUsageMB(pid);\n expect(mem).toBe(0);\n });\n\n it('handles concurrent calls on dying process', async () => {\n const child = spawn('sleep', ['10']);\n const pid = child.pid!;\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n child.kill('SIGKILL');\n\n const results = await Promise.all([\n getChildMemoryUsageMB(pid),\n getChildMemoryUsageMB(pid),\n getChildMemoryUsageMB(pid),\n ]);\n\n await exitPromise;\n expect(results.every((r) => r === 0)).toBe(true);\n });\n});\n\ndescribe('IPC send on dead process', () => {\n it('child.connected becomes false when child dies', async () => {\n const child = fork(childScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n await new Promise((r) => setTimeout(r, 50));\n expect(child.connected).toBe(true);\n\n child.kill('SIGKILL');\n await exitPromise;\n\n expect(child.connected).toBe(false);\n });\n\n it('checking connected before send prevents crash', async () => {\n const child = fork(childScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n const exitPromise = new Promise<void>((r) => child.on('exit', r));\n\n // Suppress EPIPE errors that can occur due to race conditions between\n // child.connected check and the actual pipe state\n child.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code !== 'EPIPE') throw err;\n });\n\n let sent = 0;\n let skipped = 0;\n\n const interval = setInterval(() => {\n if (child.connected) {\n child.send({ ping: Date.now() });\n sent++;\n } else {\n skipped++;\n }\n }, 20);\n\n await new Promise((r) => setTimeout(r, 60));\n child.kill('SIGKILL');\n await exitPromise;\n await new Promise((r) => setTimeout(r, 80));\n clearInterval(interval);\n\n expect(sent).toBeGreaterThan(0);\n expect(skipped).toBeGreaterThan(0);\n });\n});\n\ndescribe('init timeout rejection handling', () => {\n it('does not produce unhandled rejection when init times out', async () => {\n // Regression test: before the fix, run() was called without await in start().\n // When init timed out, the rejection in run()'s `await this.init.await` escaped\n // as an unhandled rejection — crashing the Node.js process.\n const unhandled: unknown[] = [];\n const handler = (reason: unknown) => unhandled.push(reason);\n process.on('unhandledRejection', handler);\n\n // Child that responds AFTER the timeout — simulates slow init under CPU pressure.\n // Timeout fires at 50ms (init.reject), child responds at 200ms (once() resolves).\n // Before the fix, init.reject caused an unhandled rejection in run().\n const slowScript = join(tmpdir(), 'test_slow_init_child.mjs');\n writeFileSync(\n slowScript,\n `process.on('message', () => {\n setTimeout(() => process.send({ case: 'initializeResponse' }), 200);\n });\n setInterval(() => {}, 1000);`,\n );\n\n const { SupervisedProc } = await import('./supervised_proc.js');\n class TestProc extends SupervisedProc {\n createProcess() {\n return fork(slowScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n }\n async mainTask() {}\n }\n\n const proc = new TestProc(\n 50, // initializeTimeout — fires before child responds at 200ms\n 1000, // closeTimeout\n 0, // memoryWarnMB\n 0, // memoryLimitMB\n 5000, // pingInterval\n 60000, // pingTimeout\n 2500, // highPingThreshold\n );\n\n await proc.start();\n // initialize() returns normally: child responds at 200ms, once() resolves,\n // but init was already rejected at 50ms — run() gets the rejection.\n await proc.initialize();\n\n // Give the event loop a tick for any unhandled rejection to surface\n await new Promise((r) => setTimeout(r, 100));\n\n process.off('unhandledRejection', handler);\n proc.proc?.kill();\n try {\n unlinkSync(slowScript);\n } catch {}\n\n expect(unhandled).toEqual([]);\n });\n\n it('join() resolves after init timeout instead of hanging forever', async () => {\n // When run() fails early (before registering proc event handlers),\n // #join must still resolve so that join() and close() don't hang.\n const slowScript = join(tmpdir(), 'test_slow_init_child_join.mjs');\n writeFileSync(\n slowScript,\n `process.on('message', () => {\n setTimeout(() => process.send({ case: 'initializeResponse' }), 200);\n });\n setInterval(() => {}, 1000);`,\n );\n\n const { SupervisedProc } = await import('./supervised_proc.js');\n class TestProc extends SupervisedProc {\n createProcess() {\n return fork(slowScript, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });\n }\n async mainTask() {}\n }\n\n const proc = new TestProc(50, 1000, 0, 0, 5000, 60000, 2500);\n\n await proc.start();\n await proc.initialize();\n\n // join() must resolve within a reasonable time, not hang forever\n const result = await Promise.race([\n proc.join().then(() => 'resolved'),\n new Promise((r) => setTimeout(() => r('timeout'), 2000)),\n ]);\n\n proc.proc?.kill();\n try {\n unlinkSync(slowScript);\n } catch {}\n\n expect(result).toBe('resolved');\n });\n});\n\ndescribe('timer cleanup', () => {\n it('clearInterval stops the interval', async () => {\n let count = 0;\n const interval = setInterval(() => count++, 30);\n\n await new Promise((r) => setTimeout(r, 80));\n const countAtClear = count;\n clearInterval(interval);\n\n await new Promise((r) => setTimeout(r, 80));\n expect(count).toBe(countAtClear);\n });\n\n it('double clear is safe', () => {\n const interval = setInterval(() => {}, 100);\n const timeout = setTimeout(() => {}, 1000);\n\n clearInterval(interval);\n clearTimeout(timeout);\n\n expect(() => {\n clearInterval(interval);\n clearTimeout(timeout);\n }).not.toThrow();\n });\n});\n"],"mappings":"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","names":[]}
|
package/dist/job.cjs
CHANGED
|
@@ -222,7 +222,8 @@ class JobContext {
|
|
|
222
222
|
chatHistory: targetSession.history.copy(),
|
|
223
223
|
startedAt: targetSession._startedAt,
|
|
224
224
|
audioRecordingPath: recorderIO == null ? void 0 : recorderIO.outputPath,
|
|
225
|
-
audioRecordingStartedAt: recorderIO == null ? void 0 : recorderIO.recordingStartedAt
|
|
225
|
+
audioRecordingStartedAt: recorderIO == null ? void 0 : recorderIO.recordingStartedAt,
|
|
226
|
+
modelUsage: targetSession._usageCollector.flatten()
|
|
226
227
|
});
|
|
227
228
|
}
|
|
228
229
|
async _onSessionEnd() {
|
package/dist/job.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/job.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type * as proto from '@livekit/protocol';\nimport type {\n E2EEOptions,\n LocalParticipant,\n RemoteParticipant,\n Room,\n RtcConfiguration,\n} from '@livekit/rtc-node';\nimport { ParticipantKind, RoomEvent, TrackKind } from '@livekit/rtc-node';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { Logger } from 'pino';\nimport type { InferenceExecutor } from './ipc/inference_executor.js';\nimport { log } from './log.js';\nimport { flushOtelLogs, setupCloudTracer, uploadSessionReport } from './telemetry/index.js';\nimport { isCloud } from './utils.js';\nimport type { AgentSession } from './voice/agent_session.js';\nimport { type SessionReport, createSessionReport } from './voice/report.js';\n\n// AsyncLocalStorage for job context, similar to Python's contextvars\nconst jobContextStorage = new AsyncLocalStorage<JobContext>();\n\n/**\n * Returns the current job context.\n *\n * @throws {Error} if no job context is found\n */\nexport function getJobContext(): JobContext {\n const ctx = jobContextStorage.getStore();\n if (!ctx) {\n throw new Error('no job context found, are you running this code inside a job entrypoint?');\n }\n return ctx;\n}\n\n/**\n * Runs a function within a job context, similar to Python's contextvars.\n * @internal\n */\nexport function runWithJobContext<T>(context: JobContext, fn: () => T): T {\n return jobContextStorage.run(context, fn);\n}\n\n/**\n * Runs an async function within a job context, similar to Python's contextvars.\n * @internal\n */\nexport function runWithJobContextAsync<T>(context: JobContext, fn: () => Promise<T>): Promise<T> {\n return jobContextStorage.run(context, fn);\n}\n\n/** Which tracks, if any, should the agent automatically subscribe to? */\nexport enum AutoSubscribe {\n SUBSCRIBE_ALL,\n SUBSCRIBE_NONE,\n VIDEO_ONLY,\n AUDIO_ONLY,\n}\n\nexport type JobAcceptArguments = {\n name: string;\n identity: string;\n metadata: string;\n attributes?: { [key: string]: string };\n};\n\nexport type RunningJobInfo = {\n acceptArguments: JobAcceptArguments;\n job: proto.Job;\n url: string;\n token: string;\n workerId: string;\n};\n\n/** Attempted to add a function callback, but the function already exists. */\nexport class FunctionExistsError extends Error {\n constructor(msg?: string) {\n super(msg);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** The job and environment context as seen by the agent, accessible by the entrypoint function. */\nexport class JobContext {\n #proc: JobProcess;\n #info: RunningJobInfo;\n #room: Room;\n #onConnect: () => void;\n #onShutdown: (s: string) => void;\n /** @internal */\n shutdownCallbacks: (() => Promise<void>)[] = [];\n #participantEntrypoints: ((job: JobContext, p: RemoteParticipant) => Promise<void>)[] = [];\n #participantTasks: {\n [id: string]: {\n callback: (job: JobContext, p: RemoteParticipant) => Promise<void>;\n result: Promise<void>;\n };\n } = {};\n #logger: Logger;\n #inferenceExecutor: InferenceExecutor;\n\n /** @internal */\n _primaryAgentSession?: AgentSession;\n\n /** @internal */\n _sessionDirectory: string;\n\n private connected: boolean = false;\n\n constructor(\n proc: JobProcess,\n info: RunningJobInfo,\n room: Room,\n onConnect: () => void,\n onShutdown: (s: string) => void,\n inferenceExecutor: InferenceExecutor,\n ) {\n this.#proc = proc;\n this.#info = info;\n this.#room = room;\n this.#onConnect = onConnect;\n this.#onShutdown = onShutdown;\n this.onParticipantConnected = this.onParticipantConnected.bind(this);\n this.#room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.#logger = log().child({\n jobId: this.#info.job.id,\n roomName: this.#info.job.room?.name,\n });\n this.#inferenceExecutor = inferenceExecutor;\n this._sessionDirectory = path.join(os.tmpdir(), 'livekit-agents', `job-${this.#info.job.id}`);\n }\n\n get proc(): JobProcess {\n return this.#proc;\n }\n\n get job(): proto.Job {\n return this.#info.job;\n }\n\n get workerId(): string {\n return this.#info.workerId;\n }\n\n /** @returns The room the agent was called into */\n get room(): Room {\n return this.#room;\n }\n\n get info(): RunningJobInfo {\n return this.#info;\n }\n\n /** @returns The agent's participant if connected to the room, otherwise `undefined` */\n get agent(): LocalParticipant | undefined {\n return this.#room.localParticipant;\n }\n\n /** @returns The global inference executor */\n get inferenceExecutor(): InferenceExecutor {\n return this.#inferenceExecutor;\n }\n\n /**\n * @returns The session directory for storing recordings and session data.\n */\n get sessionDirectory(): string {\n return this._sessionDirectory;\n }\n\n /** Adds a promise to be awaited when {@link JobContext.shutdown | shutdown} is called. */\n addShutdownCallback(callback: () => Promise<void>) {\n this.shutdownCallbacks.push(callback);\n }\n\n async waitForParticipant(identity?: string): Promise<RemoteParticipant> {\n if (!this.#room.isConnected) {\n throw new Error('room is not connected');\n }\n\n for (const p of this.#room.remoteParticipants.values()) {\n if ((!identity || p.identity === identity) && p.info.kind != ParticipantKind.AGENT) {\n return p;\n }\n }\n\n return new Promise((resolve, reject) => {\n const onParticipantConnected = (participant: RemoteParticipant) => {\n if (\n (!identity || participant.identity === identity) &&\n participant.info.kind != ParticipantKind.AGENT\n ) {\n clearHandlers();\n resolve(participant);\n }\n };\n const onDisconnected = () => {\n clearHandlers();\n reject(new Error('Room disconnected while waiting for participant'));\n };\n\n const clearHandlers = () => {\n this.#room.off(RoomEvent.ParticipantConnected, onParticipantConnected);\n this.#room.off(RoomEvent.Disconnected, onDisconnected);\n };\n\n this.#room.on(RoomEvent.ParticipantConnected, onParticipantConnected);\n this.#room.on(RoomEvent.Disconnected, onDisconnected);\n });\n }\n\n /**\n * Connects the agent to the room.\n *\n * @remarks\n * It is recommended to run this command as early in the function as possible, as executing it\n * later may cause noticeable delay between user and agent joins.\n *\n * @see {@link https://github.com/livekit/node-sdks/tree/main/packages/livekit-rtc#readme |\n * @livekit/rtc-node} for more information about the parameters.\n */\n async connect(\n e2ee?: E2EEOptions,\n autoSubscribe: AutoSubscribe = AutoSubscribe.SUBSCRIBE_ALL,\n rtcConfig?: RtcConfiguration,\n ) {\n if (this.connected) {\n return;\n }\n\n const opts = {\n e2ee,\n autoSubscribe: autoSubscribe == AutoSubscribe.SUBSCRIBE_ALL,\n rtcConfig,\n dynacast: false,\n };\n\n await this.#room.connect(this.#info.url, this.#info.token, opts);\n this.#onConnect();\n\n this.#room.remoteParticipants.forEach(this.onParticipantConnected);\n\n if ([AutoSubscribe.AUDIO_ONLY, AutoSubscribe.VIDEO_ONLY].includes(autoSubscribe)) {\n this.#room.remoteParticipants.forEach((p) => {\n p.trackPublications.forEach((pub) => {\n if (\n (autoSubscribe === AutoSubscribe.AUDIO_ONLY && pub.kind === TrackKind.KIND_AUDIO) ||\n (autoSubscribe === AutoSubscribe.VIDEO_ONLY && pub.kind === TrackKind.KIND_VIDEO)\n ) {\n pub.setSubscribed(true);\n }\n });\n });\n }\n this.connected = true;\n }\n\n makeSessionReport(session?: AgentSession): SessionReport {\n const targetSession = session || this._primaryAgentSession;\n\n if (!targetSession) {\n throw new Error('Cannot prepare report, no AgentSession was found');\n }\n\n const recorderIO = targetSession._recorderIO;\n\n if (recorderIO && recorderIO.recording) {\n throw new Error('Cannot create the AgentSession report, the RecorderIO is still recording');\n }\n\n return createSessionReport({\n jobId: this.job.id,\n roomId: this.job.room?.sid || '',\n room: this.job.room?.name || '',\n options: targetSession.sessionOptions,\n events: targetSession._recordedEvents,\n enableRecording: targetSession._enableRecording,\n chatHistory: targetSession.history.copy(),\n startedAt: targetSession._startedAt,\n audioRecordingPath: recorderIO?.outputPath,\n audioRecordingStartedAt: recorderIO?.recordingStartedAt,\n });\n }\n\n async _onSessionEnd(): Promise<void> {\n const session = this._primaryAgentSession;\n if (!session) {\n return;\n }\n\n const report = this.makeSessionReport(session);\n\n // TODO(brian): Implement CLI/console\n\n // Upload session report to LiveKit Cloud if enabled\n const url = new URL(this.#info.url);\n\n if (report.enableRecording && isCloud(url)) {\n try {\n await uploadSessionReport({\n agentName: this.job.agentName,\n cloudHostname: url.hostname,\n report,\n });\n this.#logger.info(\n {\n jobId: report.jobId,\n roomId: report.roomId,\n },\n 'Session report uploaded to LiveKit Cloud',\n );\n } catch (error) {\n this.#logger.error({ error }, 'Failed to upload session report');\n }\n }\n\n this.#logger.debug(\n {\n jobId: report.jobId,\n roomId: report.roomId,\n eventsCount: report.events.length,\n },\n 'Session ended, report generated',\n );\n\n // Explicitly clear the recorded events to avoid leaking memory\n session._recordedEvents = [];\n\n try {\n await flushOtelLogs();\n } catch (error) {\n this.#logger.error({ error }, 'Failed to flush OTEL logs');\n }\n }\n\n /**\n * Gracefully shuts down the job, and runs all shutdown promises.\n *\n * @param reason - Optional reason for shutdown\n */\n shutdown(reason = '') {\n this.#onShutdown(reason);\n }\n\n /** @internal */\n onParticipantConnected(p: RemoteParticipant) {\n for (const callback of this.#participantEntrypoints) {\n if (this.#participantTasks[p.identity!]?.callback == callback) {\n this.#logger.warn(\n 'a participant has joined before a prior prticipant task matching the same identity has finished:',\n p.identity,\n );\n }\n const result = callback(this, p);\n result.finally(() => delete this.#participantTasks[p.identity!]);\n this.#participantTasks[p.identity!] = { callback, result };\n }\n }\n\n /**\n * Adds a promise to be awaited whenever a new participant joins the room.\n *\n * @throws {@link FunctionExistsError} if an entrypoint already exists\n */\n addParticipantEntrypoint(callback: (job: JobContext, p: RemoteParticipant) => Promise<void>) {\n if (this.#participantEntrypoints.includes(callback)) {\n throw new FunctionExistsError('entrypoints cannot be added more than once');\n }\n\n this.#participantEntrypoints.push(callback);\n }\n\n async initRecording() {\n const url = new URL(this.#info.url);\n if (!isCloud(url)) {\n return;\n }\n\n this.#logger.debug({ hostname: url.hostname }, 'Configuring session recording (cloud tracer)');\n await setupCloudTracer({\n roomId: this.job.room!.sid,\n jobId: this.job.id,\n cloudHostname: url.hostname,\n });\n }\n}\n\nexport class JobProcess {\n #pid = process.pid;\n userData: { [id: string]: unknown } = {};\n\n get pid(): number {\n return this.#pid;\n }\n}\n\n/**\n * A request sent by the server to spawn a new agent job.\n *\n * @remarks\n * For most applications, this is best left to the default, which simply accepts the job and\n * handles the logic inside the entrypoint function. This class is useful for vetting which\n * requests should fill idle processes and which should be outright rejected.\n */\nexport class JobRequest {\n #job: proto.Job;\n #onReject: () => Promise<void>;\n #onAccept: (args: JobAcceptArguments) => Promise<void>;\n\n /** @internal */\n constructor(\n job: proto.Job,\n onReject: () => Promise<void>,\n onAccept: (args: JobAcceptArguments) => Promise<void>,\n ) {\n this.#job = job;\n this.#onReject = onReject;\n this.#onAccept = onAccept;\n }\n\n /** @returns The ID of the job, set by the LiveKit server */\n get id(): string {\n return this.#job.id;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get job(): proto.Job {\n return this.#job;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get room(): proto.Room | undefined {\n return this.#job.room;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get publisher(): proto.ParticipantInfo | undefined {\n return this.#job.participant;\n }\n\n /** @returns The agent's name, as set in {@link WorkerOptions} */\n get agentName(): string {\n return this.#job.agentName;\n }\n\n /** Rejects the job. */\n async reject() {\n await this.#onReject();\n }\n\n /** Accepts the job, launching it on an idle child process. */\n async accept(name = '', identity = '', metadata = '', attributes?: { [key: string]: string }) {\n if (identity === '') identity = 'agent-' + this.id;\n\n this.#onAccept({ name, identity, metadata, attributes });\n }\n}\n"],"mappings":"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","names":["AutoSubscribe"]}
|
|
1
|
+
{"version":3,"sources":["../src/job.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type * as proto from '@livekit/protocol';\nimport type {\n E2EEOptions,\n LocalParticipant,\n RemoteParticipant,\n Room,\n RtcConfiguration,\n} from '@livekit/rtc-node';\nimport { ParticipantKind, RoomEvent, TrackKind } from '@livekit/rtc-node';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { Logger } from 'pino';\nimport type { InferenceExecutor } from './ipc/inference_executor.js';\nimport { log } from './log.js';\nimport { flushOtelLogs, setupCloudTracer, uploadSessionReport } from './telemetry/index.js';\nimport { isCloud } from './utils.js';\nimport type { AgentSession } from './voice/agent_session.js';\nimport { type SessionReport, createSessionReport } from './voice/report.js';\n\n// AsyncLocalStorage for job context, similar to Python's contextvars\nconst jobContextStorage = new AsyncLocalStorage<JobContext>();\n\n/**\n * Returns the current job context.\n *\n * @throws {Error} if no job context is found\n */\nexport function getJobContext(): JobContext {\n const ctx = jobContextStorage.getStore();\n if (!ctx) {\n throw new Error('no job context found, are you running this code inside a job entrypoint?');\n }\n return ctx;\n}\n\n/**\n * Runs a function within a job context, similar to Python's contextvars.\n * @internal\n */\nexport function runWithJobContext<T>(context: JobContext, fn: () => T): T {\n return jobContextStorage.run(context, fn);\n}\n\n/**\n * Runs an async function within a job context, similar to Python's contextvars.\n * @internal\n */\nexport function runWithJobContextAsync<T>(context: JobContext, fn: () => Promise<T>): Promise<T> {\n return jobContextStorage.run(context, fn);\n}\n\n/** Which tracks, if any, should the agent automatically subscribe to? */\nexport enum AutoSubscribe {\n SUBSCRIBE_ALL,\n SUBSCRIBE_NONE,\n VIDEO_ONLY,\n AUDIO_ONLY,\n}\n\nexport type JobAcceptArguments = {\n name: string;\n identity: string;\n metadata: string;\n attributes?: { [key: string]: string };\n};\n\nexport type RunningJobInfo = {\n acceptArguments: JobAcceptArguments;\n job: proto.Job;\n url: string;\n token: string;\n workerId: string;\n};\n\n/** Attempted to add a function callback, but the function already exists. */\nexport class FunctionExistsError extends Error {\n constructor(msg?: string) {\n super(msg);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** The job and environment context as seen by the agent, accessible by the entrypoint function. */\nexport class JobContext {\n #proc: JobProcess;\n #info: RunningJobInfo;\n #room: Room;\n #onConnect: () => void;\n #onShutdown: (s: string) => void;\n /** @internal */\n shutdownCallbacks: (() => Promise<void>)[] = [];\n #participantEntrypoints: ((job: JobContext, p: RemoteParticipant) => Promise<void>)[] = [];\n #participantTasks: {\n [id: string]: {\n callback: (job: JobContext, p: RemoteParticipant) => Promise<void>;\n result: Promise<void>;\n };\n } = {};\n #logger: Logger;\n #inferenceExecutor: InferenceExecutor;\n\n /** @internal */\n _primaryAgentSession?: AgentSession;\n\n /** @internal */\n _sessionDirectory: string;\n\n private connected: boolean = false;\n\n constructor(\n proc: JobProcess,\n info: RunningJobInfo,\n room: Room,\n onConnect: () => void,\n onShutdown: (s: string) => void,\n inferenceExecutor: InferenceExecutor,\n ) {\n this.#proc = proc;\n this.#info = info;\n this.#room = room;\n this.#onConnect = onConnect;\n this.#onShutdown = onShutdown;\n this.onParticipantConnected = this.onParticipantConnected.bind(this);\n this.#room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.#logger = log().child({\n jobId: this.#info.job.id,\n roomName: this.#info.job.room?.name,\n });\n this.#inferenceExecutor = inferenceExecutor;\n this._sessionDirectory = path.join(os.tmpdir(), 'livekit-agents', `job-${this.#info.job.id}`);\n }\n\n get proc(): JobProcess {\n return this.#proc;\n }\n\n get job(): proto.Job {\n return this.#info.job;\n }\n\n get workerId(): string {\n return this.#info.workerId;\n }\n\n /** @returns The room the agent was called into */\n get room(): Room {\n return this.#room;\n }\n\n get info(): RunningJobInfo {\n return this.#info;\n }\n\n /** @returns The agent's participant if connected to the room, otherwise `undefined` */\n get agent(): LocalParticipant | undefined {\n return this.#room.localParticipant;\n }\n\n /** @returns The global inference executor */\n get inferenceExecutor(): InferenceExecutor {\n return this.#inferenceExecutor;\n }\n\n /**\n * @returns The session directory for storing recordings and session data.\n */\n get sessionDirectory(): string {\n return this._sessionDirectory;\n }\n\n /** Adds a promise to be awaited when {@link JobContext.shutdown | shutdown} is called. */\n addShutdownCallback(callback: () => Promise<void>) {\n this.shutdownCallbacks.push(callback);\n }\n\n async waitForParticipant(identity?: string): Promise<RemoteParticipant> {\n if (!this.#room.isConnected) {\n throw new Error('room is not connected');\n }\n\n for (const p of this.#room.remoteParticipants.values()) {\n if ((!identity || p.identity === identity) && p.info.kind != ParticipantKind.AGENT) {\n return p;\n }\n }\n\n return new Promise((resolve, reject) => {\n const onParticipantConnected = (participant: RemoteParticipant) => {\n if (\n (!identity || participant.identity === identity) &&\n participant.info.kind != ParticipantKind.AGENT\n ) {\n clearHandlers();\n resolve(participant);\n }\n };\n const onDisconnected = () => {\n clearHandlers();\n reject(new Error('Room disconnected while waiting for participant'));\n };\n\n const clearHandlers = () => {\n this.#room.off(RoomEvent.ParticipantConnected, onParticipantConnected);\n this.#room.off(RoomEvent.Disconnected, onDisconnected);\n };\n\n this.#room.on(RoomEvent.ParticipantConnected, onParticipantConnected);\n this.#room.on(RoomEvent.Disconnected, onDisconnected);\n });\n }\n\n /**\n * Connects the agent to the room.\n *\n * @remarks\n * It is recommended to run this command as early in the function as possible, as executing it\n * later may cause noticeable delay between user and agent joins.\n *\n * @see {@link https://github.com/livekit/node-sdks/tree/main/packages/livekit-rtc#readme |\n * @livekit/rtc-node} for more information about the parameters.\n */\n async connect(\n e2ee?: E2EEOptions,\n autoSubscribe: AutoSubscribe = AutoSubscribe.SUBSCRIBE_ALL,\n rtcConfig?: RtcConfiguration,\n ) {\n if (this.connected) {\n return;\n }\n\n const opts = {\n e2ee,\n autoSubscribe: autoSubscribe == AutoSubscribe.SUBSCRIBE_ALL,\n rtcConfig,\n dynacast: false,\n };\n\n await this.#room.connect(this.#info.url, this.#info.token, opts);\n this.#onConnect();\n\n this.#room.remoteParticipants.forEach(this.onParticipantConnected);\n\n if ([AutoSubscribe.AUDIO_ONLY, AutoSubscribe.VIDEO_ONLY].includes(autoSubscribe)) {\n this.#room.remoteParticipants.forEach((p) => {\n p.trackPublications.forEach((pub) => {\n if (\n (autoSubscribe === AutoSubscribe.AUDIO_ONLY && pub.kind === TrackKind.KIND_AUDIO) ||\n (autoSubscribe === AutoSubscribe.VIDEO_ONLY && pub.kind === TrackKind.KIND_VIDEO)\n ) {\n pub.setSubscribed(true);\n }\n });\n });\n }\n this.connected = true;\n }\n\n makeSessionReport(session?: AgentSession): SessionReport {\n const targetSession = session || this._primaryAgentSession;\n\n if (!targetSession) {\n throw new Error('Cannot prepare report, no AgentSession was found');\n }\n\n const recorderIO = targetSession._recorderIO;\n\n if (recorderIO && recorderIO.recording) {\n throw new Error('Cannot create the AgentSession report, the RecorderIO is still recording');\n }\n\n return createSessionReport({\n jobId: this.job.id,\n roomId: this.job.room?.sid || '',\n room: this.job.room?.name || '',\n options: targetSession.sessionOptions,\n events: targetSession._recordedEvents,\n enableRecording: targetSession._enableRecording,\n chatHistory: targetSession.history.copy(),\n startedAt: targetSession._startedAt,\n audioRecordingPath: recorderIO?.outputPath,\n audioRecordingStartedAt: recorderIO?.recordingStartedAt,\n modelUsage: targetSession._usageCollector.flatten(),\n });\n }\n\n async _onSessionEnd(): Promise<void> {\n const session = this._primaryAgentSession;\n if (!session) {\n return;\n }\n\n const report = this.makeSessionReport(session);\n\n // TODO(brian): Implement CLI/console\n\n // Upload session report to LiveKit Cloud if enabled\n const url = new URL(this.#info.url);\n\n if (report.enableRecording && isCloud(url)) {\n try {\n await uploadSessionReport({\n agentName: this.job.agentName,\n cloudHostname: url.hostname,\n report,\n });\n this.#logger.info(\n {\n jobId: report.jobId,\n roomId: report.roomId,\n },\n 'Session report uploaded to LiveKit Cloud',\n );\n } catch (error) {\n this.#logger.error({ error }, 'Failed to upload session report');\n }\n }\n\n this.#logger.debug(\n {\n jobId: report.jobId,\n roomId: report.roomId,\n eventsCount: report.events.length,\n },\n 'Session ended, report generated',\n );\n\n // Explicitly clear the recorded events to avoid leaking memory\n session._recordedEvents = [];\n\n try {\n await flushOtelLogs();\n } catch (error) {\n this.#logger.error({ error }, 'Failed to flush OTEL logs');\n }\n }\n\n /**\n * Gracefully shuts down the job, and runs all shutdown promises.\n *\n * @param reason - Optional reason for shutdown\n */\n shutdown(reason = '') {\n this.#onShutdown(reason);\n }\n\n /** @internal */\n onParticipantConnected(p: RemoteParticipant) {\n for (const callback of this.#participantEntrypoints) {\n if (this.#participantTasks[p.identity!]?.callback == callback) {\n this.#logger.warn(\n 'a participant has joined before a prior prticipant task matching the same identity has finished:',\n p.identity,\n );\n }\n const result = callback(this, p);\n result.finally(() => delete this.#participantTasks[p.identity!]);\n this.#participantTasks[p.identity!] = { callback, result };\n }\n }\n\n /**\n * Adds a promise to be awaited whenever a new participant joins the room.\n *\n * @throws {@link FunctionExistsError} if an entrypoint already exists\n */\n addParticipantEntrypoint(callback: (job: JobContext, p: RemoteParticipant) => Promise<void>) {\n if (this.#participantEntrypoints.includes(callback)) {\n throw new FunctionExistsError('entrypoints cannot be added more than once');\n }\n\n this.#participantEntrypoints.push(callback);\n }\n\n async initRecording() {\n const url = new URL(this.#info.url);\n if (!isCloud(url)) {\n return;\n }\n\n this.#logger.debug({ hostname: url.hostname }, 'Configuring session recording (cloud tracer)');\n await setupCloudTracer({\n roomId: this.job.room!.sid,\n jobId: this.job.id,\n cloudHostname: url.hostname,\n });\n }\n}\n\nexport class JobProcess {\n #pid = process.pid;\n userData: { [id: string]: unknown } = {};\n\n get pid(): number {\n return this.#pid;\n }\n}\n\n/**\n * A request sent by the server to spawn a new agent job.\n *\n * @remarks\n * For most applications, this is best left to the default, which simply accepts the job and\n * handles the logic inside the entrypoint function. This class is useful for vetting which\n * requests should fill idle processes and which should be outright rejected.\n */\nexport class JobRequest {\n #job: proto.Job;\n #onReject: () => Promise<void>;\n #onAccept: (args: JobAcceptArguments) => Promise<void>;\n\n /** @internal */\n constructor(\n job: proto.Job,\n onReject: () => Promise<void>,\n onAccept: (args: JobAcceptArguments) => Promise<void>,\n ) {\n this.#job = job;\n this.#onReject = onReject;\n this.#onAccept = onAccept;\n }\n\n /** @returns The ID of the job, set by the LiveKit server */\n get id(): string {\n return this.#job.id;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get job(): proto.Job {\n return this.#job;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get room(): proto.Room | undefined {\n return this.#job.room;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get publisher(): proto.ParticipantInfo | undefined {\n return this.#job.participant;\n }\n\n /** @returns The agent's name, as set in {@link WorkerOptions} */\n get agentName(): string {\n return this.#job.agentName;\n }\n\n /** Rejects the job. */\n async reject() {\n await this.#onReject();\n }\n\n /** Accepts the job, launching it on an idle child process. */\n async accept(name = '', identity = '', metadata = '', attributes?: { [key: string]: string }) {\n if (identity === '') identity = 'agent-' + this.id;\n\n this.#onAccept({ name, identity, metadata, attributes });\n }\n}\n"],"mappings":"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","names":["AutoSubscribe"]}
|
package/dist/job.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job.d.ts","sourceRoot":"","sources":["../src/job.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"job.d.ts","sourceRoot":"","sources":["../src/job.ts"],"names":[],"mappings":"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"}
|
package/dist/job.js
CHANGED
|
@@ -182,7 +182,8 @@ class JobContext {
|
|
|
182
182
|
chatHistory: targetSession.history.copy(),
|
|
183
183
|
startedAt: targetSession._startedAt,
|
|
184
184
|
audioRecordingPath: recorderIO == null ? void 0 : recorderIO.outputPath,
|
|
185
|
-
audioRecordingStartedAt: recorderIO == null ? void 0 : recorderIO.recordingStartedAt
|
|
185
|
+
audioRecordingStartedAt: recorderIO == null ? void 0 : recorderIO.recordingStartedAt,
|
|
186
|
+
modelUsage: targetSession._usageCollector.flatten()
|
|
186
187
|
});
|
|
187
188
|
}
|
|
188
189
|
async _onSessionEnd() {
|
package/dist/job.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/job.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type * as proto from '@livekit/protocol';\nimport type {\n E2EEOptions,\n LocalParticipant,\n RemoteParticipant,\n Room,\n RtcConfiguration,\n} from '@livekit/rtc-node';\nimport { ParticipantKind, RoomEvent, TrackKind } from '@livekit/rtc-node';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { Logger } from 'pino';\nimport type { InferenceExecutor } from './ipc/inference_executor.js';\nimport { log } from './log.js';\nimport { flushOtelLogs, setupCloudTracer, uploadSessionReport } from './telemetry/index.js';\nimport { isCloud } from './utils.js';\nimport type { AgentSession } from './voice/agent_session.js';\nimport { type SessionReport, createSessionReport } from './voice/report.js';\n\n// AsyncLocalStorage for job context, similar to Python's contextvars\nconst jobContextStorage = new AsyncLocalStorage<JobContext>();\n\n/**\n * Returns the current job context.\n *\n * @throws {Error} if no job context is found\n */\nexport function getJobContext(): JobContext {\n const ctx = jobContextStorage.getStore();\n if (!ctx) {\n throw new Error('no job context found, are you running this code inside a job entrypoint?');\n }\n return ctx;\n}\n\n/**\n * Runs a function within a job context, similar to Python's contextvars.\n * @internal\n */\nexport function runWithJobContext<T>(context: JobContext, fn: () => T): T {\n return jobContextStorage.run(context, fn);\n}\n\n/**\n * Runs an async function within a job context, similar to Python's contextvars.\n * @internal\n */\nexport function runWithJobContextAsync<T>(context: JobContext, fn: () => Promise<T>): Promise<T> {\n return jobContextStorage.run(context, fn);\n}\n\n/** Which tracks, if any, should the agent automatically subscribe to? */\nexport enum AutoSubscribe {\n SUBSCRIBE_ALL,\n SUBSCRIBE_NONE,\n VIDEO_ONLY,\n AUDIO_ONLY,\n}\n\nexport type JobAcceptArguments = {\n name: string;\n identity: string;\n metadata: string;\n attributes?: { [key: string]: string };\n};\n\nexport type RunningJobInfo = {\n acceptArguments: JobAcceptArguments;\n job: proto.Job;\n url: string;\n token: string;\n workerId: string;\n};\n\n/** Attempted to add a function callback, but the function already exists. */\nexport class FunctionExistsError extends Error {\n constructor(msg?: string) {\n super(msg);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** The job and environment context as seen by the agent, accessible by the entrypoint function. */\nexport class JobContext {\n #proc: JobProcess;\n #info: RunningJobInfo;\n #room: Room;\n #onConnect: () => void;\n #onShutdown: (s: string) => void;\n /** @internal */\n shutdownCallbacks: (() => Promise<void>)[] = [];\n #participantEntrypoints: ((job: JobContext, p: RemoteParticipant) => Promise<void>)[] = [];\n #participantTasks: {\n [id: string]: {\n callback: (job: JobContext, p: RemoteParticipant) => Promise<void>;\n result: Promise<void>;\n };\n } = {};\n #logger: Logger;\n #inferenceExecutor: InferenceExecutor;\n\n /** @internal */\n _primaryAgentSession?: AgentSession;\n\n /** @internal */\n _sessionDirectory: string;\n\n private connected: boolean = false;\n\n constructor(\n proc: JobProcess,\n info: RunningJobInfo,\n room: Room,\n onConnect: () => void,\n onShutdown: (s: string) => void,\n inferenceExecutor: InferenceExecutor,\n ) {\n this.#proc = proc;\n this.#info = info;\n this.#room = room;\n this.#onConnect = onConnect;\n this.#onShutdown = onShutdown;\n this.onParticipantConnected = this.onParticipantConnected.bind(this);\n this.#room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.#logger = log().child({\n jobId: this.#info.job.id,\n roomName: this.#info.job.room?.name,\n });\n this.#inferenceExecutor = inferenceExecutor;\n this._sessionDirectory = path.join(os.tmpdir(), 'livekit-agents', `job-${this.#info.job.id}`);\n }\n\n get proc(): JobProcess {\n return this.#proc;\n }\n\n get job(): proto.Job {\n return this.#info.job;\n }\n\n get workerId(): string {\n return this.#info.workerId;\n }\n\n /** @returns The room the agent was called into */\n get room(): Room {\n return this.#room;\n }\n\n get info(): RunningJobInfo {\n return this.#info;\n }\n\n /** @returns The agent's participant if connected to the room, otherwise `undefined` */\n get agent(): LocalParticipant | undefined {\n return this.#room.localParticipant;\n }\n\n /** @returns The global inference executor */\n get inferenceExecutor(): InferenceExecutor {\n return this.#inferenceExecutor;\n }\n\n /**\n * @returns The session directory for storing recordings and session data.\n */\n get sessionDirectory(): string {\n return this._sessionDirectory;\n }\n\n /** Adds a promise to be awaited when {@link JobContext.shutdown | shutdown} is called. */\n addShutdownCallback(callback: () => Promise<void>) {\n this.shutdownCallbacks.push(callback);\n }\n\n async waitForParticipant(identity?: string): Promise<RemoteParticipant> {\n if (!this.#room.isConnected) {\n throw new Error('room is not connected');\n }\n\n for (const p of this.#room.remoteParticipants.values()) {\n if ((!identity || p.identity === identity) && p.info.kind != ParticipantKind.AGENT) {\n return p;\n }\n }\n\n return new Promise((resolve, reject) => {\n const onParticipantConnected = (participant: RemoteParticipant) => {\n if (\n (!identity || participant.identity === identity) &&\n participant.info.kind != ParticipantKind.AGENT\n ) {\n clearHandlers();\n resolve(participant);\n }\n };\n const onDisconnected = () => {\n clearHandlers();\n reject(new Error('Room disconnected while waiting for participant'));\n };\n\n const clearHandlers = () => {\n this.#room.off(RoomEvent.ParticipantConnected, onParticipantConnected);\n this.#room.off(RoomEvent.Disconnected, onDisconnected);\n };\n\n this.#room.on(RoomEvent.ParticipantConnected, onParticipantConnected);\n this.#room.on(RoomEvent.Disconnected, onDisconnected);\n });\n }\n\n /**\n * Connects the agent to the room.\n *\n * @remarks\n * It is recommended to run this command as early in the function as possible, as executing it\n * later may cause noticeable delay between user and agent joins.\n *\n * @see {@link https://github.com/livekit/node-sdks/tree/main/packages/livekit-rtc#readme |\n * @livekit/rtc-node} for more information about the parameters.\n */\n async connect(\n e2ee?: E2EEOptions,\n autoSubscribe: AutoSubscribe = AutoSubscribe.SUBSCRIBE_ALL,\n rtcConfig?: RtcConfiguration,\n ) {\n if (this.connected) {\n return;\n }\n\n const opts = {\n e2ee,\n autoSubscribe: autoSubscribe == AutoSubscribe.SUBSCRIBE_ALL,\n rtcConfig,\n dynacast: false,\n };\n\n await this.#room.connect(this.#info.url, this.#info.token, opts);\n this.#onConnect();\n\n this.#room.remoteParticipants.forEach(this.onParticipantConnected);\n\n if ([AutoSubscribe.AUDIO_ONLY, AutoSubscribe.VIDEO_ONLY].includes(autoSubscribe)) {\n this.#room.remoteParticipants.forEach((p) => {\n p.trackPublications.forEach((pub) => {\n if (\n (autoSubscribe === AutoSubscribe.AUDIO_ONLY && pub.kind === TrackKind.KIND_AUDIO) ||\n (autoSubscribe === AutoSubscribe.VIDEO_ONLY && pub.kind === TrackKind.KIND_VIDEO)\n ) {\n pub.setSubscribed(true);\n }\n });\n });\n }\n this.connected = true;\n }\n\n makeSessionReport(session?: AgentSession): SessionReport {\n const targetSession = session || this._primaryAgentSession;\n\n if (!targetSession) {\n throw new Error('Cannot prepare report, no AgentSession was found');\n }\n\n const recorderIO = targetSession._recorderIO;\n\n if (recorderIO && recorderIO.recording) {\n throw new Error('Cannot create the AgentSession report, the RecorderIO is still recording');\n }\n\n return createSessionReport({\n jobId: this.job.id,\n roomId: this.job.room?.sid || '',\n room: this.job.room?.name || '',\n options: targetSession.sessionOptions,\n events: targetSession._recordedEvents,\n enableRecording: targetSession._enableRecording,\n chatHistory: targetSession.history.copy(),\n startedAt: targetSession._startedAt,\n audioRecordingPath: recorderIO?.outputPath,\n audioRecordingStartedAt: recorderIO?.recordingStartedAt,\n });\n }\n\n async _onSessionEnd(): Promise<void> {\n const session = this._primaryAgentSession;\n if (!session) {\n return;\n }\n\n const report = this.makeSessionReport(session);\n\n // TODO(brian): Implement CLI/console\n\n // Upload session report to LiveKit Cloud if enabled\n const url = new URL(this.#info.url);\n\n if (report.enableRecording && isCloud(url)) {\n try {\n await uploadSessionReport({\n agentName: this.job.agentName,\n cloudHostname: url.hostname,\n report,\n });\n this.#logger.info(\n {\n jobId: report.jobId,\n roomId: report.roomId,\n },\n 'Session report uploaded to LiveKit Cloud',\n );\n } catch (error) {\n this.#logger.error({ error }, 'Failed to upload session report');\n }\n }\n\n this.#logger.debug(\n {\n jobId: report.jobId,\n roomId: report.roomId,\n eventsCount: report.events.length,\n },\n 'Session ended, report generated',\n );\n\n // Explicitly clear the recorded events to avoid leaking memory\n session._recordedEvents = [];\n\n try {\n await flushOtelLogs();\n } catch (error) {\n this.#logger.error({ error }, 'Failed to flush OTEL logs');\n }\n }\n\n /**\n * Gracefully shuts down the job, and runs all shutdown promises.\n *\n * @param reason - Optional reason for shutdown\n */\n shutdown(reason = '') {\n this.#onShutdown(reason);\n }\n\n /** @internal */\n onParticipantConnected(p: RemoteParticipant) {\n for (const callback of this.#participantEntrypoints) {\n if (this.#participantTasks[p.identity!]?.callback == callback) {\n this.#logger.warn(\n 'a participant has joined before a prior prticipant task matching the same identity has finished:',\n p.identity,\n );\n }\n const result = callback(this, p);\n result.finally(() => delete this.#participantTasks[p.identity!]);\n this.#participantTasks[p.identity!] = { callback, result };\n }\n }\n\n /**\n * Adds a promise to be awaited whenever a new participant joins the room.\n *\n * @throws {@link FunctionExistsError} if an entrypoint already exists\n */\n addParticipantEntrypoint(callback: (job: JobContext, p: RemoteParticipant) => Promise<void>) {\n if (this.#participantEntrypoints.includes(callback)) {\n throw new FunctionExistsError('entrypoints cannot be added more than once');\n }\n\n this.#participantEntrypoints.push(callback);\n }\n\n async initRecording() {\n const url = new URL(this.#info.url);\n if (!isCloud(url)) {\n return;\n }\n\n this.#logger.debug({ hostname: url.hostname }, 'Configuring session recording (cloud tracer)');\n await setupCloudTracer({\n roomId: this.job.room!.sid,\n jobId: this.job.id,\n cloudHostname: url.hostname,\n });\n }\n}\n\nexport class JobProcess {\n #pid = process.pid;\n userData: { [id: string]: unknown } = {};\n\n get pid(): number {\n return this.#pid;\n }\n}\n\n/**\n * A request sent by the server to spawn a new agent job.\n *\n * @remarks\n * For most applications, this is best left to the default, which simply accepts the job and\n * handles the logic inside the entrypoint function. This class is useful for vetting which\n * requests should fill idle processes and which should be outright rejected.\n */\nexport class JobRequest {\n #job: proto.Job;\n #onReject: () => Promise<void>;\n #onAccept: (args: JobAcceptArguments) => Promise<void>;\n\n /** @internal */\n constructor(\n job: proto.Job,\n onReject: () => Promise<void>,\n onAccept: (args: JobAcceptArguments) => Promise<void>,\n ) {\n this.#job = job;\n this.#onReject = onReject;\n this.#onAccept = onAccept;\n }\n\n /** @returns The ID of the job, set by the LiveKit server */\n get id(): string {\n return this.#job.id;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get job(): proto.Job {\n return this.#job;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get room(): proto.Room | undefined {\n return this.#job.room;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get publisher(): proto.ParticipantInfo | undefined {\n return this.#job.participant;\n }\n\n /** @returns The agent's name, as set in {@link WorkerOptions} */\n get agentName(): string {\n return this.#job.agentName;\n }\n\n /** Rejects the job. */\n async reject() {\n await this.#onReject();\n }\n\n /** Accepts the job, launching it on an idle child process. */\n async accept(name = '', identity = '', metadata = '', attributes?: { [key: string]: string }) {\n if (identity === '') identity = 'agent-' + this.id;\n\n this.#onAccept({ name, identity, metadata, attributes });\n }\n}\n"],"mappings":"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","names":["AutoSubscribe"]}
|
|
1
|
+
{"version":3,"sources":["../src/job.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type * as proto from '@livekit/protocol';\nimport type {\n E2EEOptions,\n LocalParticipant,\n RemoteParticipant,\n Room,\n RtcConfiguration,\n} from '@livekit/rtc-node';\nimport { ParticipantKind, RoomEvent, TrackKind } from '@livekit/rtc-node';\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\nimport type { Logger } from 'pino';\nimport type { InferenceExecutor } from './ipc/inference_executor.js';\nimport { log } from './log.js';\nimport { flushOtelLogs, setupCloudTracer, uploadSessionReport } from './telemetry/index.js';\nimport { isCloud } from './utils.js';\nimport type { AgentSession } from './voice/agent_session.js';\nimport { type SessionReport, createSessionReport } from './voice/report.js';\n\n// AsyncLocalStorage for job context, similar to Python's contextvars\nconst jobContextStorage = new AsyncLocalStorage<JobContext>();\n\n/**\n * Returns the current job context.\n *\n * @throws {Error} if no job context is found\n */\nexport function getJobContext(): JobContext {\n const ctx = jobContextStorage.getStore();\n if (!ctx) {\n throw new Error('no job context found, are you running this code inside a job entrypoint?');\n }\n return ctx;\n}\n\n/**\n * Runs a function within a job context, similar to Python's contextvars.\n * @internal\n */\nexport function runWithJobContext<T>(context: JobContext, fn: () => T): T {\n return jobContextStorage.run(context, fn);\n}\n\n/**\n * Runs an async function within a job context, similar to Python's contextvars.\n * @internal\n */\nexport function runWithJobContextAsync<T>(context: JobContext, fn: () => Promise<T>): Promise<T> {\n return jobContextStorage.run(context, fn);\n}\n\n/** Which tracks, if any, should the agent automatically subscribe to? */\nexport enum AutoSubscribe {\n SUBSCRIBE_ALL,\n SUBSCRIBE_NONE,\n VIDEO_ONLY,\n AUDIO_ONLY,\n}\n\nexport type JobAcceptArguments = {\n name: string;\n identity: string;\n metadata: string;\n attributes?: { [key: string]: string };\n};\n\nexport type RunningJobInfo = {\n acceptArguments: JobAcceptArguments;\n job: proto.Job;\n url: string;\n token: string;\n workerId: string;\n};\n\n/** Attempted to add a function callback, but the function already exists. */\nexport class FunctionExistsError extends Error {\n constructor(msg?: string) {\n super(msg);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** The job and environment context as seen by the agent, accessible by the entrypoint function. */\nexport class JobContext {\n #proc: JobProcess;\n #info: RunningJobInfo;\n #room: Room;\n #onConnect: () => void;\n #onShutdown: (s: string) => void;\n /** @internal */\n shutdownCallbacks: (() => Promise<void>)[] = [];\n #participantEntrypoints: ((job: JobContext, p: RemoteParticipant) => Promise<void>)[] = [];\n #participantTasks: {\n [id: string]: {\n callback: (job: JobContext, p: RemoteParticipant) => Promise<void>;\n result: Promise<void>;\n };\n } = {};\n #logger: Logger;\n #inferenceExecutor: InferenceExecutor;\n\n /** @internal */\n _primaryAgentSession?: AgentSession;\n\n /** @internal */\n _sessionDirectory: string;\n\n private connected: boolean = false;\n\n constructor(\n proc: JobProcess,\n info: RunningJobInfo,\n room: Room,\n onConnect: () => void,\n onShutdown: (s: string) => void,\n inferenceExecutor: InferenceExecutor,\n ) {\n this.#proc = proc;\n this.#info = info;\n this.#room = room;\n this.#onConnect = onConnect;\n this.#onShutdown = onShutdown;\n this.onParticipantConnected = this.onParticipantConnected.bind(this);\n this.#room.on(RoomEvent.ParticipantConnected, this.onParticipantConnected);\n this.#logger = log().child({\n jobId: this.#info.job.id,\n roomName: this.#info.job.room?.name,\n });\n this.#inferenceExecutor = inferenceExecutor;\n this._sessionDirectory = path.join(os.tmpdir(), 'livekit-agents', `job-${this.#info.job.id}`);\n }\n\n get proc(): JobProcess {\n return this.#proc;\n }\n\n get job(): proto.Job {\n return this.#info.job;\n }\n\n get workerId(): string {\n return this.#info.workerId;\n }\n\n /** @returns The room the agent was called into */\n get room(): Room {\n return this.#room;\n }\n\n get info(): RunningJobInfo {\n return this.#info;\n }\n\n /** @returns The agent's participant if connected to the room, otherwise `undefined` */\n get agent(): LocalParticipant | undefined {\n return this.#room.localParticipant;\n }\n\n /** @returns The global inference executor */\n get inferenceExecutor(): InferenceExecutor {\n return this.#inferenceExecutor;\n }\n\n /**\n * @returns The session directory for storing recordings and session data.\n */\n get sessionDirectory(): string {\n return this._sessionDirectory;\n }\n\n /** Adds a promise to be awaited when {@link JobContext.shutdown | shutdown} is called. */\n addShutdownCallback(callback: () => Promise<void>) {\n this.shutdownCallbacks.push(callback);\n }\n\n async waitForParticipant(identity?: string): Promise<RemoteParticipant> {\n if (!this.#room.isConnected) {\n throw new Error('room is not connected');\n }\n\n for (const p of this.#room.remoteParticipants.values()) {\n if ((!identity || p.identity === identity) && p.info.kind != ParticipantKind.AGENT) {\n return p;\n }\n }\n\n return new Promise((resolve, reject) => {\n const onParticipantConnected = (participant: RemoteParticipant) => {\n if (\n (!identity || participant.identity === identity) &&\n participant.info.kind != ParticipantKind.AGENT\n ) {\n clearHandlers();\n resolve(participant);\n }\n };\n const onDisconnected = () => {\n clearHandlers();\n reject(new Error('Room disconnected while waiting for participant'));\n };\n\n const clearHandlers = () => {\n this.#room.off(RoomEvent.ParticipantConnected, onParticipantConnected);\n this.#room.off(RoomEvent.Disconnected, onDisconnected);\n };\n\n this.#room.on(RoomEvent.ParticipantConnected, onParticipantConnected);\n this.#room.on(RoomEvent.Disconnected, onDisconnected);\n });\n }\n\n /**\n * Connects the agent to the room.\n *\n * @remarks\n * It is recommended to run this command as early in the function as possible, as executing it\n * later may cause noticeable delay between user and agent joins.\n *\n * @see {@link https://github.com/livekit/node-sdks/tree/main/packages/livekit-rtc#readme |\n * @livekit/rtc-node} for more information about the parameters.\n */\n async connect(\n e2ee?: E2EEOptions,\n autoSubscribe: AutoSubscribe = AutoSubscribe.SUBSCRIBE_ALL,\n rtcConfig?: RtcConfiguration,\n ) {\n if (this.connected) {\n return;\n }\n\n const opts = {\n e2ee,\n autoSubscribe: autoSubscribe == AutoSubscribe.SUBSCRIBE_ALL,\n rtcConfig,\n dynacast: false,\n };\n\n await this.#room.connect(this.#info.url, this.#info.token, opts);\n this.#onConnect();\n\n this.#room.remoteParticipants.forEach(this.onParticipantConnected);\n\n if ([AutoSubscribe.AUDIO_ONLY, AutoSubscribe.VIDEO_ONLY].includes(autoSubscribe)) {\n this.#room.remoteParticipants.forEach((p) => {\n p.trackPublications.forEach((pub) => {\n if (\n (autoSubscribe === AutoSubscribe.AUDIO_ONLY && pub.kind === TrackKind.KIND_AUDIO) ||\n (autoSubscribe === AutoSubscribe.VIDEO_ONLY && pub.kind === TrackKind.KIND_VIDEO)\n ) {\n pub.setSubscribed(true);\n }\n });\n });\n }\n this.connected = true;\n }\n\n makeSessionReport(session?: AgentSession): SessionReport {\n const targetSession = session || this._primaryAgentSession;\n\n if (!targetSession) {\n throw new Error('Cannot prepare report, no AgentSession was found');\n }\n\n const recorderIO = targetSession._recorderIO;\n\n if (recorderIO && recorderIO.recording) {\n throw new Error('Cannot create the AgentSession report, the RecorderIO is still recording');\n }\n\n return createSessionReport({\n jobId: this.job.id,\n roomId: this.job.room?.sid || '',\n room: this.job.room?.name || '',\n options: targetSession.sessionOptions,\n events: targetSession._recordedEvents,\n enableRecording: targetSession._enableRecording,\n chatHistory: targetSession.history.copy(),\n startedAt: targetSession._startedAt,\n audioRecordingPath: recorderIO?.outputPath,\n audioRecordingStartedAt: recorderIO?.recordingStartedAt,\n modelUsage: targetSession._usageCollector.flatten(),\n });\n }\n\n async _onSessionEnd(): Promise<void> {\n const session = this._primaryAgentSession;\n if (!session) {\n return;\n }\n\n const report = this.makeSessionReport(session);\n\n // TODO(brian): Implement CLI/console\n\n // Upload session report to LiveKit Cloud if enabled\n const url = new URL(this.#info.url);\n\n if (report.enableRecording && isCloud(url)) {\n try {\n await uploadSessionReport({\n agentName: this.job.agentName,\n cloudHostname: url.hostname,\n report,\n });\n this.#logger.info(\n {\n jobId: report.jobId,\n roomId: report.roomId,\n },\n 'Session report uploaded to LiveKit Cloud',\n );\n } catch (error) {\n this.#logger.error({ error }, 'Failed to upload session report');\n }\n }\n\n this.#logger.debug(\n {\n jobId: report.jobId,\n roomId: report.roomId,\n eventsCount: report.events.length,\n },\n 'Session ended, report generated',\n );\n\n // Explicitly clear the recorded events to avoid leaking memory\n session._recordedEvents = [];\n\n try {\n await flushOtelLogs();\n } catch (error) {\n this.#logger.error({ error }, 'Failed to flush OTEL logs');\n }\n }\n\n /**\n * Gracefully shuts down the job, and runs all shutdown promises.\n *\n * @param reason - Optional reason for shutdown\n */\n shutdown(reason = '') {\n this.#onShutdown(reason);\n }\n\n /** @internal */\n onParticipantConnected(p: RemoteParticipant) {\n for (const callback of this.#participantEntrypoints) {\n if (this.#participantTasks[p.identity!]?.callback == callback) {\n this.#logger.warn(\n 'a participant has joined before a prior prticipant task matching the same identity has finished:',\n p.identity,\n );\n }\n const result = callback(this, p);\n result.finally(() => delete this.#participantTasks[p.identity!]);\n this.#participantTasks[p.identity!] = { callback, result };\n }\n }\n\n /**\n * Adds a promise to be awaited whenever a new participant joins the room.\n *\n * @throws {@link FunctionExistsError} if an entrypoint already exists\n */\n addParticipantEntrypoint(callback: (job: JobContext, p: RemoteParticipant) => Promise<void>) {\n if (this.#participantEntrypoints.includes(callback)) {\n throw new FunctionExistsError('entrypoints cannot be added more than once');\n }\n\n this.#participantEntrypoints.push(callback);\n }\n\n async initRecording() {\n const url = new URL(this.#info.url);\n if (!isCloud(url)) {\n return;\n }\n\n this.#logger.debug({ hostname: url.hostname }, 'Configuring session recording (cloud tracer)');\n await setupCloudTracer({\n roomId: this.job.room!.sid,\n jobId: this.job.id,\n cloudHostname: url.hostname,\n });\n }\n}\n\nexport class JobProcess {\n #pid = process.pid;\n userData: { [id: string]: unknown } = {};\n\n get pid(): number {\n return this.#pid;\n }\n}\n\n/**\n * A request sent by the server to spawn a new agent job.\n *\n * @remarks\n * For most applications, this is best left to the default, which simply accepts the job and\n * handles the logic inside the entrypoint function. This class is useful for vetting which\n * requests should fill idle processes and which should be outright rejected.\n */\nexport class JobRequest {\n #job: proto.Job;\n #onReject: () => Promise<void>;\n #onAccept: (args: JobAcceptArguments) => Promise<void>;\n\n /** @internal */\n constructor(\n job: proto.Job,\n onReject: () => Promise<void>,\n onAccept: (args: JobAcceptArguments) => Promise<void>,\n ) {\n this.#job = job;\n this.#onReject = onReject;\n this.#onAccept = onAccept;\n }\n\n /** @returns The ID of the job, set by the LiveKit server */\n get id(): string {\n return this.#job.id;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get job(): proto.Job {\n return this.#job;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get room(): proto.Room | undefined {\n return this.#job.room;\n }\n\n /** @see {@link https://www.npmjs.com/package/@livekit/protocol | @livekit/protocol} */\n get publisher(): proto.ParticipantInfo | undefined {\n return this.#job.participant;\n }\n\n /** @returns The agent's name, as set in {@link WorkerOptions} */\n get agentName(): string {\n return this.#job.agentName;\n }\n\n /** Rejects the job. */\n async reject() {\n await this.#onReject();\n }\n\n /** Accepts the job, launching it on an idle child process. */\n async accept(name = '', identity = '', metadata = '', attributes?: { [key: string]: string }) {\n if (identity === '') identity = 'agent-' + this.id;\n\n this.#onAccept({ name, identity, metadata, attributes });\n }\n}\n"],"mappings":"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","names":["AutoSubscribe"]}
|