@livekit/agents 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +21 -0
  3. package/LICENSE +201 -0
  4. package/dist/audio.d.ts +12 -0
  5. package/dist/audio.d.ts.map +1 -0
  6. package/dist/audio.js +37 -0
  7. package/dist/audio.js.map +1 -0
  8. package/dist/cli.d.ts +11 -0
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +68 -8
  11. package/dist/cli.js.map +1 -1
  12. package/dist/generator.d.ts +12 -6
  13. package/dist/generator.d.ts.map +1 -1
  14. package/dist/generator.js +9 -3
  15. package/dist/generator.js.map +1 -1
  16. package/dist/http_server.d.ts +1 -1
  17. package/dist/http_server.js +0 -3
  18. package/dist/http_server.js.map +1 -1
  19. package/dist/index.d.ts +12 -3
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +12 -3
  22. package/dist/index.js.map +1 -1
  23. package/dist/ipc/job_executor.d.ts +19 -0
  24. package/dist/ipc/job_executor.d.ts.map +1 -0
  25. package/dist/ipc/job_executor.js +8 -0
  26. package/dist/ipc/job_executor.js.map +1 -0
  27. package/dist/ipc/job_main.d.ts +7 -4
  28. package/dist/ipc/job_main.d.ts.map +1 -1
  29. package/dist/ipc/job_main.js +96 -61
  30. package/dist/ipc/job_main.js.map +1 -1
  31. package/dist/ipc/message.d.ts +41 -0
  32. package/dist/ipc/message.d.ts.map +1 -0
  33. package/dist/ipc/message.js +2 -0
  34. package/dist/ipc/message.js.map +1 -0
  35. package/dist/ipc/proc_job_executor.d.ts +15 -0
  36. package/dist/ipc/proc_job_executor.d.ts.map +1 -0
  37. package/dist/ipc/proc_job_executor.js +150 -0
  38. package/dist/ipc/proc_job_executor.js.map +1 -0
  39. package/dist/ipc/proc_pool.d.ts +26 -0
  40. package/dist/ipc/proc_pool.d.ts.map +1 -0
  41. package/dist/ipc/proc_pool.js +82 -0
  42. package/dist/ipc/proc_pool.js.map +1 -0
  43. package/dist/job.d.ts +99 -0
  44. package/dist/job.d.ts.map +1 -0
  45. package/dist/job.js +197 -0
  46. package/dist/job.js.map +1 -0
  47. package/dist/llm/function_context.d.ts +20 -0
  48. package/dist/llm/function_context.d.ts.map +1 -0
  49. package/dist/llm/function_context.js +37 -0
  50. package/dist/llm/function_context.js.map +1 -0
  51. package/dist/llm/index.d.ts +3 -0
  52. package/dist/llm/index.d.ts.map +1 -0
  53. package/dist/llm/index.js +6 -0
  54. package/dist/llm/index.js.map +1 -0
  55. package/dist/log.d.ts +12 -1
  56. package/dist/log.d.ts.map +1 -1
  57. package/dist/log.js +28 -11
  58. package/dist/log.js.map +1 -1
  59. package/dist/plugin.js +20 -7
  60. package/dist/plugin.js.map +1 -1
  61. package/dist/stt/index.d.ts +1 -1
  62. package/dist/stt/index.d.ts.map +1 -1
  63. package/dist/stt/index.js.map +1 -1
  64. package/dist/stt/stream_adapter.d.ts +2 -11
  65. package/dist/stt/stream_adapter.d.ts.map +1 -1
  66. package/dist/stt/stream_adapter.js +47 -33
  67. package/dist/stt/stream_adapter.js.map +1 -1
  68. package/dist/stt/stt.d.ts +27 -0
  69. package/dist/stt/stt.d.ts.map +1 -1
  70. package/dist/stt/stt.js +32 -5
  71. package/dist/stt/stt.js.map +1 -1
  72. package/dist/tts/stream_adapter.d.ts +4 -11
  73. package/dist/tts/stream_adapter.d.ts.map +1 -1
  74. package/dist/tts/stream_adapter.js +66 -32
  75. package/dist/tts/stream_adapter.js.map +1 -1
  76. package/dist/tts/tts.d.ts +10 -0
  77. package/dist/tts/tts.d.ts.map +1 -1
  78. package/dist/tts/tts.js +48 -7
  79. package/dist/tts/tts.js.map +1 -1
  80. package/dist/utils.d.ts +32 -0
  81. package/dist/utils.d.ts.map +1 -1
  82. package/dist/utils.js +114 -6
  83. package/dist/utils.js.map +1 -1
  84. package/dist/vad.d.ts +29 -0
  85. package/dist/vad.d.ts.map +1 -1
  86. package/dist/vad.js.map +1 -1
  87. package/dist/worker.d.ts +67 -50
  88. package/dist/worker.d.ts.map +1 -1
  89. package/dist/worker.js +379 -214
  90. package/dist/worker.js.map +1 -1
  91. package/package.json +9 -9
  92. package/src/audio.ts +62 -0
  93. package/src/cli.ts +72 -8
  94. package/src/generator.ts +13 -7
  95. package/src/index.ts +13 -3
  96. package/src/ipc/job_executor.ts +25 -0
  97. package/src/ipc/job_main.ts +134 -61
  98. package/src/ipc/message.ts +39 -0
  99. package/src/ipc/proc_job_executor.ts +162 -0
  100. package/src/ipc/proc_pool.ts +108 -0
  101. package/src/job.ts +258 -0
  102. package/src/llm/function_context.ts +61 -0
  103. package/src/llm/index.ts +11 -0
  104. package/src/log.ts +40 -8
  105. package/src/stt/index.ts +1 -1
  106. package/src/stt/stream_adapter.ts +32 -32
  107. package/src/stt/stt.ts +27 -0
  108. package/src/tts/stream_adapter.ts +32 -31
  109. package/src/tts/tts.ts +10 -0
  110. package/src/utils.ts +125 -3
  111. package/src/vad.ts +29 -0
  112. package/src/worker.ts +419 -170
  113. package/tsconfig.json +6 -0
  114. package/dist/ipc/job_process.d.ts +0 -22
  115. package/dist/ipc/job_process.d.ts.map +0 -1
  116. package/dist/ipc/job_process.js +0 -73
  117. package/dist/ipc/job_process.js.map +0 -1
  118. package/dist/ipc/protocol.d.ts +0 -40
  119. package/dist/ipc/protocol.d.ts.map +0 -1
  120. package/dist/ipc/protocol.js +0 -14
  121. package/dist/ipc/protocol.js.map +0 -1
  122. package/dist/job_context.d.ts +0 -16
  123. package/dist/job_context.d.ts.map +0 -1
  124. package/dist/job_context.js +0 -31
  125. package/dist/job_context.js.map +0 -1
  126. package/dist/job_request.d.ts +0 -42
  127. package/dist/job_request.d.ts.map +0 -1
  128. package/dist/job_request.js +0 -79
  129. package/dist/job_request.js.map +0 -1
  130. package/src/ipc/job_process.ts +0 -96
  131. package/src/ipc/protocol.ts +0 -51
  132. package/src/job_context.ts +0 -49
  133. package/src/job_request.ts +0 -118
package/dist/worker.js CHANGED
@@ -1,55 +1,99 @@
1
- // SPDX-FileCopyrightText: 2024 LiveKit, Inc.
2
- //
3
- // SPDX-License-Identifier: Apache-2.0
4
- import { JobType, ParticipantPermission, ServerMessage, WorkerMessage, } from '@livekit/protocol';
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _Worker_instances, _Worker_opts, _Worker_procPool, _Worker_id, _Worker_closed, _Worker_draining, _Worker_connecting, _Worker_tasks, _Worker_pending, _Worker_close, _Worker_session, _Worker_httpServer, _Worker_logger, _Worker_runWS, _Worker_availability, _Worker_termination;
13
+ import { JobType, ParticipantPermission, ServerMessage, WorkerMessage, WorkerStatus, } from '@livekit/protocol';
5
14
  import { EventEmitter } from 'events';
6
- import { AccessToken } from 'livekit-server-sdk';
15
+ import { AccessToken, RoomServiceClient } from 'livekit-server-sdk';
7
16
  import os from 'os';
8
17
  import { WebSocket } from 'ws';
9
18
  import { HTTPServer } from './http_server.js';
10
- import { JobProcess } from './ipc/job_process.js';
11
- import { JobRequest } from './job_request.js';
19
+ import { ProcPool } from './ipc/proc_pool.js';
20
+ import { JobRequest } from './job.js';
12
21
  import { log } from './log.js';
22
+ import { Future } from './utils.js';
13
23
  import { version } from './version.js';
14
24
  const MAX_RECONNECT_ATTEMPTS = 10;
15
- const ASSIGNMENT_TIMEOUT = 15 * 1000;
16
- const LOAD_INTERVAL = 5 * 1000;
17
- const cpuLoad = () => (os
18
- .cpus()
19
- .reduce((acc, x) => acc + x.times.idle / Object.values(x.times).reduce((acc, x) => acc + x, 0), 0) /
20
- os.cpus().length) *
21
- 100;
22
- class WorkerPermissions {
23
- canPublish;
24
- canSubscribe;
25
- canPublishData;
26
- canUpdateMetadata;
27
- hidden;
28
- constructor(canPublish = true, canSubscribe = true, canPublishData = true, canUpdateMetadata = true, hidden = false) {
25
+ const ASSIGNMENT_TIMEOUT = 7.5 * 1000;
26
+ const UPDATE_LOAD_INTERVAL = 2.5 * 1000;
27
+ /** Necessary credentials not provided and not found in an appropriate environmental variable. */
28
+ export class MissingCredentialsError extends Error {
29
+ constructor(msg) {
30
+ super(msg);
31
+ Object.setPrototypeOf(this, new.target.prototype);
32
+ }
33
+ }
34
+ /** Worker did not run as expected. */
35
+ export class WorkerError extends Error {
36
+ constructor(msg) {
37
+ super(msg);
38
+ Object.setPrototypeOf(this, new.target.prototype);
39
+ }
40
+ }
41
+ /** @internal */
42
+ export const defaultInitializeProcessFunc = (_) => _;
43
+ const defaultRequestFunc = async (ctx) => {
44
+ await ctx.accept();
45
+ };
46
+ const defaultCpuLoad = async () => {
47
+ return new Promise((resolve) => {
48
+ const cpus1 = os.cpus();
49
+ setTimeout(() => {
50
+ const cpus2 = os.cpus();
51
+ let idle = 0;
52
+ let total = 0;
53
+ for (let i = 0; i < cpus1.length; i++) {
54
+ const cpu1 = cpus1[i].times;
55
+ const cpu2 = cpus2[i].times;
56
+ idle += cpu2.idle - cpu1.idle;
57
+ const total1 = Object.values(cpu1).reduce((acc, i) => acc + i, 0);
58
+ const total2 = Object.values(cpu2).reduce((acc, i) => acc + i, 0);
59
+ total += total2 - total1;
60
+ }
61
+ resolve(+(1 - idle / total).toFixed(2));
62
+ }, UPDATE_LOAD_INTERVAL);
63
+ });
64
+ };
65
+ /** Participant permissions to pass to every agent spun up by this worker. */
66
+ export class WorkerPermissions {
67
+ constructor(canPublish = true, canSubscribe = true, canPublishData = true, canUpdateMetadata = true, canPublishSources = [], hidden = false) {
29
68
  this.canPublish = canPublish;
30
69
  this.canSubscribe = canSubscribe;
31
70
  this.canPublishData = canPublishData;
32
71
  this.canUpdateMetadata = canUpdateMetadata;
72
+ this.canPublishSources = canPublishSources;
33
73
  this.hidden = hidden;
34
74
  }
35
75
  }
76
+ /**
77
+ * Data class describing worker behaviour.
78
+ *
79
+ * @remarks
80
+ * The Agents framework provides sane worker defaults, and works out-of-the-box with no tweaking
81
+ * necessary. The only mandatory parameter is `agent`, which points to the entry function.
82
+ *
83
+ * This class is mostly useful in conjunction with {@link cli.runApp}.
84
+ */
36
85
  export class WorkerOptions {
37
- requestFunc;
38
- cpuLoadFunc;
39
- namespace;
40
- permissions;
41
- workerType;
42
- maxRetry;
43
- wsURL;
44
- apiKey;
45
- apiSecret;
46
- host;
47
- port;
48
- constructor({ requestFunc, cpuLoadFunc = cpuLoad, namespace = 'default', permissions = new WorkerPermissions(), workerType = JobType.JT_PUBLISHER, maxRetry = MAX_RECONNECT_ATTEMPTS, wsURL = 'ws://localhost:7880', apiKey = undefined, apiSecret = undefined, host = 'localhost', port = 8081, }) {
86
+ /** @param options */
87
+ constructor({ agent, requestFunc = defaultRequestFunc, loadFunc = defaultCpuLoad, loadThreshold = 0.65, numIdleProcesses = 3, shutdownProcessTimeout = 60 * 1000, initializeProcessTimeout = 10 * 1000, permissions = new WorkerPermissions(), agentName = '', workerType = JobType.JT_ROOM, maxRetry = MAX_RECONNECT_ATTEMPTS, wsURL = 'ws://localhost:7880', apiKey = undefined, apiSecret = undefined, host = 'localhost', port = 8081, logLevel = 'info', }) {
88
+ this.agent = agent;
49
89
  this.requestFunc = requestFunc;
50
- this.cpuLoadFunc = cpuLoadFunc;
51
- this.namespace = namespace;
90
+ this.loadFunc = loadFunc;
91
+ this.loadThreshold = loadThreshold;
92
+ this.numIdleProcesses = numIdleProcesses;
93
+ this.shutdownProcessTimeout = shutdownProcessTimeout;
94
+ this.initializeProcessTimeout = initializeProcessTimeout;
52
95
  this.permissions = permissions;
96
+ this.agentName = agentName;
53
97
  this.workerType = workerType;
54
98
  this.maxRetry = maxRetry;
55
99
  this.wsURL = wsURL;
@@ -57,240 +101,361 @@ export class WorkerOptions {
57
101
  this.apiSecret = apiSecret;
58
102
  this.host = host;
59
103
  this.port = port;
60
- }
61
- }
62
- class ActiveJob {
63
- job;
64
- acceptData;
65
- constructor(job, acceptData) {
66
- this.job = job;
67
- this.acceptData = acceptData;
104
+ this.logLevel = logLevel;
68
105
  }
69
106
  }
70
107
  class PendingAssignment {
71
- promise = new Promise((resolve) => {
72
- this.resolve = resolve; // oh, JavaScript.
73
- });
108
+ constructor() {
109
+ this.promise = new Promise((resolve) => {
110
+ this.resolve = resolve; // this is how JavaScript lets you resolve promises externally
111
+ });
112
+ }
74
113
  resolve(arg) {
75
- arg;
114
+ arg; // useless call to counteract TypeScript E6133
76
115
  }
77
116
  }
117
+ /**
118
+ * Central orchestrator for all processes and job requests.
119
+ *
120
+ * @remarks
121
+ * For most usecases, Worker should not be initialized or handled directly; you should instead call
122
+ * for its creation through {@link cli.runApp}. This could, however, be useful in situations where
123
+ * you don't have access to a command line, such as a headless program, or one that uses Agents
124
+ * behind a wrapper.
125
+ */
78
126
  export class Worker {
79
- opts;
80
- #id = 'unregistered';
81
- session = undefined;
82
- closed = false;
83
- httpServer;
84
- logger = log.child({ version });
85
- event = new EventEmitter();
86
- pending = {};
87
- processes = {};
127
+ /* @throws {@link MissingCredentialsError} if URL, API key or API secret are missing */
88
128
  constructor(opts) {
129
+ _Worker_instances.add(this);
130
+ _Worker_opts.set(this, void 0);
131
+ _Worker_procPool.set(this, void 0);
132
+ _Worker_id.set(this, 'unregistered');
133
+ _Worker_closed.set(this, true);
134
+ _Worker_draining.set(this, false);
135
+ _Worker_connecting.set(this, false);
136
+ _Worker_tasks.set(this, []);
137
+ _Worker_pending.set(this, {});
138
+ _Worker_close.set(this, new Future());
139
+ this.event = new EventEmitter();
140
+ _Worker_session.set(this, undefined);
141
+ _Worker_httpServer.set(this, void 0);
142
+ _Worker_logger.set(this, log().child({ version }));
89
143
  opts.wsURL = opts.wsURL || process.env.LIVEKIT_URL || '';
90
144
  opts.apiKey = opts.apiKey || process.env.LIVEKIT_API_KEY || '';
91
145
  opts.apiSecret = opts.apiSecret || process.env.LIVEKIT_API_SECRET || '';
92
- this.opts = opts;
93
- this.httpServer = new HTTPServer(opts.host, opts.port);
94
- }
95
- get id() {
96
- return this.#id;
146
+ if (opts.wsURL === '')
147
+ throw new MissingCredentialsError('--url is required, or set LIVEKIT_URL env var');
148
+ if (opts.apiKey === '')
149
+ throw new MissingCredentialsError('--api-key is required, or set LIVEKIT_API_KEY env var');
150
+ if (opts.apiSecret === '')
151
+ throw new MissingCredentialsError('--api-secret is required, or set LIVEKIT_API_SECRET env var');
152
+ __classPrivateFieldSet(this, _Worker_procPool, new ProcPool(opts.agent, opts.numIdleProcesses, opts.initializeProcessTimeout, opts.shutdownProcessTimeout), "f");
153
+ __classPrivateFieldSet(this, _Worker_opts, opts, "f");
154
+ __classPrivateFieldSet(this, _Worker_httpServer, new HTTPServer(opts.host, opts.port), "f");
97
155
  }
156
+ /* @throws {@link WorkerError} if worker failed to connect or already running */
98
157
  async run() {
99
- this.logger.info('starting worker');
100
- if (this.opts.wsURL === '')
101
- throw new Error('--url is required, or set LIVEKIT_URL env var');
102
- if (this.opts.apiKey === '')
103
- throw new Error('--api-key is required, or set LIVEKIT_API_KEY env var');
104
- if (this.opts.apiSecret === '')
105
- throw new Error('--api-secret is required, or set LIVEKIT_API_SECRET env var');
158
+ if (!__classPrivateFieldGet(this, _Worker_closed, "f")) {
159
+ throw new WorkerError('worker is already running');
160
+ }
161
+ __classPrivateFieldGet(this, _Worker_logger, "f").info('starting worker');
162
+ __classPrivateFieldSet(this, _Worker_closed, false, "f");
163
+ __classPrivateFieldGet(this, _Worker_procPool, "f").start();
106
164
  const workerWS = async () => {
107
165
  let retries = 0;
108
- while (!this.closed) {
109
- const token = new AccessToken(this.opts.apiKey, this.opts.apiSecret);
166
+ __classPrivateFieldSet(this, _Worker_connecting, true, "f");
167
+ while (!__classPrivateFieldGet(this, _Worker_closed, "f")) {
168
+ const url = new URL(__classPrivateFieldGet(this, _Worker_opts, "f").wsURL);
169
+ url.protocol = url.protocol.replace('http', 'ws');
170
+ const token = new AccessToken(__classPrivateFieldGet(this, _Worker_opts, "f").apiKey, __classPrivateFieldGet(this, _Worker_opts, "f").apiSecret);
110
171
  token.addGrant({ agent: true });
111
172
  const jwt = await token.toJwt();
112
- const url = new URL(this.opts.wsURL);
113
- url.protocol = url.protocol.replace('http', 'ws');
114
- this.session = new WebSocket(url + 'agent', {
173
+ __classPrivateFieldSet(this, _Worker_session, new WebSocket(url + 'agent', {
115
174
  headers: { authorization: 'Bearer ' + jwt },
116
- });
175
+ }), "f");
117
176
  try {
118
177
  await new Promise((resolve, reject) => {
119
- this.session.on('open', resolve);
120
- this.session.on('error', (error) => reject(error));
121
- this.session.on('close', (code) => reject(`WebSocket returned ${code}`));
178
+ __classPrivateFieldGet(this, _Worker_session, "f").on('open', resolve);
179
+ __classPrivateFieldGet(this, _Worker_session, "f").on('error', (error) => reject(error));
180
+ __classPrivateFieldGet(this, _Worker_session, "f").on('close', (code) => reject(`WebSocket returned ${code}`));
122
181
  });
123
- this.runWS(this.session);
182
+ retries = 0;
183
+ __classPrivateFieldGet(this, _Worker_logger, "f").debug('connected to LiveKit server');
184
+ __classPrivateFieldGet(this, _Worker_instances, "m", _Worker_runWS).call(this, __classPrivateFieldGet(this, _Worker_session, "f"));
124
185
  return;
125
186
  }
126
187
  catch (e) {
127
- if (this.closed)
188
+ if (__classPrivateFieldGet(this, _Worker_closed, "f"))
128
189
  return;
129
- if (retries >= this.opts.maxRetry) {
130
- throw new Error(`failed to connect to LiveKit server after ${retries} attempts: ${e}`);
190
+ if (retries >= __classPrivateFieldGet(this, _Worker_opts, "f").maxRetry) {
191
+ throw new WorkerError(`failed to connect to LiveKit server after ${retries} attempts: ${e}`);
131
192
  }
132
193
  retries++;
133
194
  const delay = Math.min(retries * 2, 10);
134
- this.logger.warn(`failed to connect to LiveKit server, retrying in ${delay} seconds: ${e} (${retries}/${this.opts.maxRetry})`);
195
+ __classPrivateFieldGet(this, _Worker_logger, "f").warn(`failed to connect to LiveKit server, retrying in ${delay} seconds: ${e} (${retries}/${__classPrivateFieldGet(this, _Worker_opts, "f").maxRetry})`);
135
196
  await new Promise((resolve) => setTimeout(resolve, delay * 1000));
136
197
  }
137
198
  }
138
199
  };
139
- await Promise.all([workerWS(), this.httpServer.run()]);
200
+ await Promise.all([workerWS(), __classPrivateFieldGet(this, _Worker_httpServer, "f").run()]);
201
+ __classPrivateFieldGet(this, _Worker_close, "f").resolve();
140
202
  }
141
- startProcess(job, acceptData, raw) {
142
- const proc = new JobProcess(job, acceptData, raw, this.opts.wsURL);
143
- this.processes[job.id] = { proc, activeJob: new ActiveJob(job, acceptData) };
144
- proc
145
- .run()
146
- .catch((e) => {
147
- proc.logger.error(`error running job process ${proc.job.id}: ${e}`);
148
- })
149
- .finally(() => {
150
- proc.clear();
151
- delete this.processes[job.id];
152
- });
203
+ get id() {
204
+ return __classPrivateFieldGet(this, _Worker_id, "f");
153
205
  }
154
- runWS(ws) {
155
- let closingWS = false;
156
- const send = (msg) => {
157
- if (closingWS) {
158
- this.event.off('worker_msg', send);
159
- return;
160
- }
161
- ws.send(msg.toBinary());
162
- };
163
- this.event.on('worker_msg', send);
164
- ws.addEventListener('close', () => {
165
- closingWS = true;
166
- this.logger.error('worker connection closed unexpectedly');
167
- this.close();
168
- });
169
- ws.addEventListener('message', (event) => {
170
- if (event.type !== 'message') {
171
- this.logger.warn('unexpected message type: ' + event.type);
172
- return;
173
- }
174
- const msg = new ServerMessage();
175
- msg.fromBinary(event.data);
176
- switch (msg.message.case) {
177
- case 'register': {
178
- this.#id = msg.message.value.workerId;
179
- log
180
- .child({ id: this.id, server_info: msg.message.value.serverInfo })
181
- .info('registered worker');
182
- break;
183
- }
184
- case 'availability': {
185
- this.availability(msg.message.value);
186
- break;
187
- }
188
- case 'assignment': {
189
- const job = msg.message.value.job;
190
- if (job.id in this.pending) {
191
- const task = this.pending[job.id];
192
- delete this.pending[job.id];
193
- task.value.resolve({
194
- asgn: msg.message.value,
195
- raw: msg.toJsonString(),
196
- });
197
- }
198
- else {
199
- log.child({ job }).warn('received assignment for unknown job ' + job.id);
200
- }
201
- break;
206
+ get activeJobs() {
207
+ return __classPrivateFieldGet(this, _Worker_procPool, "f").processes
208
+ .filter((proc) => proc.runningJob)
209
+ .map((proc) => proc.runningJob);
210
+ }
211
+ /* @throws {@link WorkerError} if worker did not drain in time */
212
+ async drain(timeout) {
213
+ if (__classPrivateFieldGet(this, _Worker_draining, "f")) {
214
+ return;
215
+ }
216
+ __classPrivateFieldGet(this, _Worker_logger, "f").info('draining worker');
217
+ __classPrivateFieldSet(this, _Worker_draining, true, "f");
218
+ this.event.emit('worker_msg', new WorkerMessage({
219
+ message: {
220
+ case: 'updateWorker',
221
+ value: {
222
+ status: WorkerStatus.WS_FULL,
223
+ },
224
+ },
225
+ }));
226
+ const joinJobs = async () => {
227
+ return Promise.all(__classPrivateFieldGet(this, _Worker_procPool, "f").processes.map((proc) => {
228
+ if (!proc.runningJob) {
229
+ proc.close();
202
230
  }
203
- }
231
+ return proc.join();
232
+ }));
233
+ };
234
+ const timer = setTimeout(() => {
235
+ throw new WorkerError('timed out draining');
236
+ }, timeout);
237
+ if (timeout === undefined)
238
+ clearTimeout(timer);
239
+ await joinJobs().then(() => {
240
+ clearTimeout(timer);
204
241
  });
242
+ }
243
+ async simulateJob(roomName, participantIdentity) {
244
+ const client = new RoomServiceClient(__classPrivateFieldGet(this, _Worker_opts, "f").wsURL, __classPrivateFieldGet(this, _Worker_opts, "f").apiKey, __classPrivateFieldGet(this, _Worker_opts, "f").apiSecret);
245
+ const room = await client.createRoom({ name: roomName });
246
+ let participant = undefined;
247
+ if (participantIdentity) {
248
+ participant = await client.getParticipant(roomName, participantIdentity);
249
+ }
205
250
  this.event.emit('worker_msg', new WorkerMessage({
206
251
  message: {
207
- case: 'register',
252
+ case: 'simulateJob',
208
253
  value: {
209
- type: this.opts.workerType,
210
- namespace: this.opts.namespace,
211
- allowedPermissions: new ParticipantPermission({
212
- canPublish: this.opts.permissions.canPublish,
213
- canSubscribe: this.opts.permissions.canSubscribe,
214
- canPublishData: this.opts.permissions.canPublishData,
215
- hidden: this.opts.permissions.hidden,
216
- agent: true,
217
- }),
218
- version,
254
+ type: JobType.JT_PUBLISHER,
255
+ room,
256
+ participant,
219
257
  },
220
258
  },
221
259
  }));
222
- const loadMonitor = setInterval(() => {
223
- if (closingWS)
224
- clearInterval(loadMonitor);
260
+ }
261
+ async close() {
262
+ var _a;
263
+ if (__classPrivateFieldGet(this, _Worker_closed, "f")) {
264
+ await __classPrivateFieldGet(this, _Worker_close, "f").await;
265
+ return;
266
+ }
267
+ __classPrivateFieldGet(this, _Worker_logger, "f").info('shutting down worker');
268
+ __classPrivateFieldSet(this, _Worker_closed, true, "f");
269
+ await __classPrivateFieldGet(this, _Worker_procPool, "f").close();
270
+ await __classPrivateFieldGet(this, _Worker_httpServer, "f").close();
271
+ await Promise.allSettled(__classPrivateFieldGet(this, _Worker_tasks, "f"));
272
+ (_a = __classPrivateFieldGet(this, _Worker_session, "f")) === null || _a === void 0 ? void 0 : _a.close();
273
+ await __classPrivateFieldGet(this, _Worker_close, "f").await;
274
+ }
275
+ }
276
+ _Worker_opts = new WeakMap(), _Worker_procPool = new WeakMap(), _Worker_id = new WeakMap(), _Worker_closed = new WeakMap(), _Worker_draining = new WeakMap(), _Worker_connecting = new WeakMap(), _Worker_tasks = new WeakMap(), _Worker_pending = new WeakMap(), _Worker_close = new WeakMap(), _Worker_session = new WeakMap(), _Worker_httpServer = new WeakMap(), _Worker_logger = new WeakMap(), _Worker_instances = new WeakSet(), _Worker_runWS = function _Worker_runWS(ws) {
277
+ let closingWS = false;
278
+ const send = (msg) => {
279
+ if (closingWS) {
280
+ this.event.off('worker_msg', send);
281
+ return;
282
+ }
283
+ ws.send(msg.toBinary());
284
+ };
285
+ this.event.on('worker_msg', send);
286
+ ws.addEventListener('close', () => {
287
+ closingWS = true;
288
+ __classPrivateFieldGet(this, _Worker_logger, "f").error('worker connection closed unexpectedly');
289
+ this.close();
290
+ });
291
+ ws.addEventListener('message', (event) => {
292
+ if (event.type !== 'message') {
293
+ __classPrivateFieldGet(this, _Worker_logger, "f").warn('unexpected message type: ' + event.type);
294
+ return;
295
+ }
296
+ const msg = new ServerMessage();
297
+ msg.fromBinary(event.data);
298
+ // register is the only valid first message, and it is only valid as the
299
+ // first message
300
+ if (__classPrivateFieldGet(this, _Worker_connecting, "f") && msg.message.case !== 'register') {
301
+ throw new WorkerError('expected register response as first message');
302
+ }
303
+ switch (msg.message.case) {
304
+ case 'register': {
305
+ __classPrivateFieldSet(this, _Worker_id, msg.message.value.workerId, "f");
306
+ __classPrivateFieldGet(this, _Worker_logger, "f")
307
+ .child({ id: this.id, server_info: msg.message.value.serverInfo })
308
+ .info('registered worker');
309
+ this.event.emit('worker_registered', msg.message.value.workerId, msg.message.value.serverInfo);
310
+ __classPrivateFieldSet(this, _Worker_connecting, false, "f");
311
+ break;
312
+ }
313
+ case 'availability': {
314
+ const task = __classPrivateFieldGet(this, _Worker_instances, "m", _Worker_availability).call(this, msg.message.value);
315
+ __classPrivateFieldGet(this, _Worker_tasks, "f").push(task);
316
+ task.finally(() => __classPrivateFieldGet(this, _Worker_tasks, "f").splice(__classPrivateFieldGet(this, _Worker_tasks, "f").indexOf(task)));
317
+ break;
318
+ }
319
+ case 'assignment': {
320
+ const job = msg.message.value.job;
321
+ if (job.id in __classPrivateFieldGet(this, _Worker_pending, "f")) {
322
+ const task = __classPrivateFieldGet(this, _Worker_pending, "f")[job.id];
323
+ delete __classPrivateFieldGet(this, _Worker_pending, "f")[job.id];
324
+ task.resolve(msg.message.value);
325
+ }
326
+ else {
327
+ __classPrivateFieldGet(this, _Worker_logger, "f").child({ job }).warn('received assignment for unknown job ' + job.id);
328
+ }
329
+ break;
330
+ }
331
+ case 'termination': {
332
+ const task = __classPrivateFieldGet(this, _Worker_instances, "m", _Worker_termination).call(this, msg.message.value);
333
+ __classPrivateFieldGet(this, _Worker_tasks, "f").push(task);
334
+ task.finally(() => __classPrivateFieldGet(this, _Worker_tasks, "f").splice(__classPrivateFieldGet(this, _Worker_tasks, "f").indexOf(task)));
335
+ break;
336
+ }
337
+ }
338
+ });
339
+ this.event.emit('worker_msg', new WorkerMessage({
340
+ message: {
341
+ case: 'register',
342
+ value: {
343
+ type: __classPrivateFieldGet(this, _Worker_opts, "f").workerType,
344
+ agentName: __classPrivateFieldGet(this, _Worker_opts, "f").agentName,
345
+ allowedPermissions: new ParticipantPermission({
346
+ canPublish: __classPrivateFieldGet(this, _Worker_opts, "f").permissions.canPublish,
347
+ canSubscribe: __classPrivateFieldGet(this, _Worker_opts, "f").permissions.canSubscribe,
348
+ canPublishData: __classPrivateFieldGet(this, _Worker_opts, "f").permissions.canPublishData,
349
+ canUpdateMetadata: __classPrivateFieldGet(this, _Worker_opts, "f").permissions.canUpdateMetadata,
350
+ hidden: __classPrivateFieldGet(this, _Worker_opts, "f").permissions.hidden,
351
+ agent: true,
352
+ }),
353
+ version,
354
+ },
355
+ },
356
+ }));
357
+ let currentStatus = WorkerStatus.WS_AVAILABLE;
358
+ const loadMonitor = setInterval(() => {
359
+ if (closingWS)
360
+ clearInterval(loadMonitor);
361
+ const oldStatus = currentStatus;
362
+ __classPrivateFieldGet(this, _Worker_opts, "f").loadFunc().then((currentLoad) => {
363
+ const isFull = currentLoad >= __classPrivateFieldGet(this, _Worker_opts, "f").loadThreshold;
364
+ const currentlyAvailable = !isFull;
365
+ currentStatus = currentlyAvailable ? WorkerStatus.WS_AVAILABLE : WorkerStatus.WS_FULL;
366
+ if (oldStatus != currentStatus) {
367
+ const extra = { load: currentLoad, loadThreshold: __classPrivateFieldGet(this, _Worker_opts, "f").loadThreshold };
368
+ if (isFull) {
369
+ __classPrivateFieldGet(this, _Worker_logger, "f").child(extra).info('worker is at full capacity, marking as unavailable');
370
+ }
371
+ else {
372
+ __classPrivateFieldGet(this, _Worker_logger, "f").child(extra).info('worker is below capacity, marking as available');
373
+ }
374
+ }
225
375
  this.event.emit('worker_msg', new WorkerMessage({
226
376
  message: {
227
377
  case: 'updateWorker',
228
378
  value: {
229
- load: cpuLoad(),
379
+ load: currentLoad,
380
+ status: currentStatus,
230
381
  },
231
382
  },
232
383
  }));
233
- }, LOAD_INTERVAL);
234
- }
235
- async availability(msg) {
236
- const tx = new EventEmitter();
237
- const req = new JobRequest(msg.job, tx);
238
- tx.on('recv', async (av) => {
239
- const msg = new WorkerMessage({
240
- message: {
241
- case: 'availability',
242
- value: {
243
- available: av.avail,
244
- jobId: req.id,
245
- participantIdentity: av.data?.identity,
246
- participantName: av.data?.name,
247
- participantMetadata: av.data?.metadata,
248
- },
384
+ });
385
+ }, UPDATE_LOAD_INTERVAL);
386
+ }, _Worker_availability = async function _Worker_availability(msg) {
387
+ let answered = false;
388
+ const onReject = async () => {
389
+ answered = true;
390
+ this.event.emit('worker_msg', new WorkerMessage({
391
+ message: {
392
+ case: 'availability',
393
+ value: {
394
+ jobId: msg.job.id,
395
+ available: false,
396
+ },
397
+ },
398
+ }));
399
+ };
400
+ const onAccept = async (args) => {
401
+ answered = true;
402
+ this.event.emit('worker_msg', new WorkerMessage({
403
+ message: {
404
+ case: 'availability',
405
+ value: {
406
+ jobId: msg.job.id,
407
+ available: true,
408
+ participantIdentity: args.identity,
409
+ participantName: args.name,
410
+ participantMetadata: args.metadata,
249
411
  },
250
- });
251
- this.pending[req.id] = { value: new PendingAssignment() };
252
- this.event.emit('worker_msg', msg);
253
- if (!av.avail)
254
- return;
255
- const timer = setTimeout(() => {
256
- log.child({ req }).warn(`assignment for job ${req.id} timed out`);
257
- return;
258
- }, ASSIGNMENT_TIMEOUT);
259
- this.pending[req.id].value.promise.then(({ asgn, raw }) => {
260
- clearTimeout(timer);
261
- this.startProcess(asgn.job, av.data, raw);
262
- });
412
+ },
413
+ }));
414
+ __classPrivateFieldGet(this, _Worker_pending, "f")[req.id] = new PendingAssignment();
415
+ const timer = setTimeout(() => {
416
+ __classPrivateFieldGet(this, _Worker_logger, "f").child({ req }).warn(`assignment for job ${req.id} timed out`);
417
+ return;
418
+ }, ASSIGNMENT_TIMEOUT);
419
+ const asgn = await __classPrivateFieldGet(this, _Worker_pending, "f")[req.id].promise.then(async (asgn) => {
420
+ clearTimeout(timer);
421
+ return asgn;
263
422
  });
423
+ await __classPrivateFieldGet(this, _Worker_procPool, "f").launchJob({
424
+ acceptArguments: args,
425
+ job: msg.job,
426
+ url: asgn.url || __classPrivateFieldGet(this, _Worker_opts, "f").wsURL,
427
+ token: asgn.token,
428
+ });
429
+ };
430
+ const req = new JobRequest(msg.job, onReject, onAccept);
431
+ __classPrivateFieldGet(this, _Worker_logger, "f")
432
+ .child({ job: msg.job, resuming: msg.resuming, agentName: __classPrivateFieldGet(this, _Worker_opts, "f").agentName })
433
+ .info('received job request');
434
+ const jobRequestTask = async () => {
264
435
  try {
265
- this.opts.requestFunc(req);
436
+ await __classPrivateFieldGet(this, _Worker_opts, "f").requestFunc(req);
266
437
  }
267
438
  catch (e) {
268
- log.child({ req }).error(`user request handler for job ${req.id} failed`);
439
+ __classPrivateFieldGet(this, _Worker_logger, "f")
440
+ .child({ job: msg.job, resuming: msg.resuming, agentName: __classPrivateFieldGet(this, _Worker_opts, "f").agentName })
441
+ .info('jobRequestFunc failed');
442
+ await onReject();
269
443
  }
270
- finally {
271
- if (!req.answered) {
272
- log.child({ req }).error(`no answer for job ${req.id}, automatically rejecting the job`);
273
- this.event.emit('worker_msg', new WorkerMessage({
274
- message: {
275
- case: 'availability',
276
- value: {
277
- available: false,
278
- },
279
- },
280
- }));
281
- }
444
+ if (!answered) {
445
+ __classPrivateFieldGet(this, _Worker_logger, "f")
446
+ .child({ job: msg.job, resuming: msg.resuming, agentName: __classPrivateFieldGet(this, _Worker_opts, "f").agentName })
447
+ .info('no answer was given inside the jobRequestFunc, automatically rejecting the job');
282
448
  }
449
+ };
450
+ const task = jobRequestTask();
451
+ __classPrivateFieldGet(this, _Worker_tasks, "f").push(task);
452
+ task.finally(() => __classPrivateFieldGet(this, _Worker_tasks, "f").splice(__classPrivateFieldGet(this, _Worker_tasks, "f").indexOf(task)));
453
+ }, _Worker_termination = async function _Worker_termination(msg) {
454
+ const proc = __classPrivateFieldGet(this, _Worker_procPool, "f").getByJobId(msg.jobId);
455
+ if (proc === null) {
456
+ // safe to ignore
457
+ return;
283
458
  }
284
- async close() {
285
- if (this.closed)
286
- return;
287
- this.closed = true;
288
- this.logger.debug('shutting down worker');
289
- await this.httpServer.close();
290
- for await (const value of Object.values(this.processes)) {
291
- await value.proc.close();
292
- }
293
- this.session?.close();
294
- }
295
- }
459
+ await proc.close();
460
+ };
296
461
  //# sourceMappingURL=worker.js.map