@livekit/agents 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +40 -0
  3. package/dist/audio.d.ts +1 -4
  4. package/dist/audio.d.ts.map +1 -1
  5. package/dist/audio.js +30 -12
  6. package/dist/audio.js.map +1 -1
  7. package/dist/cli.d.ts +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +41 -17
  10. package/dist/cli.js.map +1 -1
  11. package/dist/generator.d.ts +5 -0
  12. package/dist/generator.d.ts.map +1 -1
  13. package/dist/generator.js +11 -0
  14. package/dist/generator.js.map +1 -1
  15. package/dist/http_server.d.ts +1 -0
  16. package/dist/http_server.d.ts.map +1 -1
  17. package/dist/http_server.js +13 -0
  18. package/dist/http_server.js.map +1 -1
  19. package/dist/index.d.ts +3 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +3 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/ipc/job_main.js +9 -1
  24. package/dist/ipc/job_main.js.map +1 -1
  25. package/dist/ipc/proc_pool.d.ts.map +1 -1
  26. package/dist/ipc/proc_pool.js +1 -0
  27. package/dist/ipc/proc_pool.js.map +1 -1
  28. package/dist/job.d.ts +1 -0
  29. package/dist/job.d.ts.map +1 -1
  30. package/dist/job.js +30 -1
  31. package/dist/job.js.map +1 -1
  32. package/dist/multimodal/agent_playout.d.ts +34 -0
  33. package/dist/multimodal/agent_playout.d.ts.map +1 -0
  34. package/dist/multimodal/agent_playout.js +221 -0
  35. package/dist/multimodal/agent_playout.js.map +1 -0
  36. package/dist/multimodal/index.d.ts +3 -0
  37. package/dist/multimodal/index.d.ts.map +1 -0
  38. package/dist/multimodal/index.js +6 -0
  39. package/dist/multimodal/index.js.map +1 -0
  40. package/dist/multimodal/multimodal_agent.d.ts +47 -0
  41. package/dist/multimodal/multimodal_agent.d.ts.map +1 -0
  42. package/dist/multimodal/multimodal_agent.js +329 -0
  43. package/dist/multimodal/multimodal_agent.js.map +1 -0
  44. package/dist/transcription.d.ts +22 -0
  45. package/dist/transcription.d.ts.map +1 -0
  46. package/dist/transcription.js +112 -0
  47. package/dist/transcription.js.map +1 -0
  48. package/dist/utils.d.ts +29 -1
  49. package/dist/utils.d.ts.map +1 -1
  50. package/dist/utils.js +117 -15
  51. package/dist/utils.js.map +1 -1
  52. package/dist/worker.d.ts +3 -1
  53. package/dist/worker.d.ts.map +1 -1
  54. package/dist/worker.js +49 -9
  55. package/dist/worker.js.map +1 -1
  56. package/package.json +6 -4
  57. package/src/audio.ts +21 -20
  58. package/src/cli.ts +42 -17
  59. package/src/generator.ts +14 -0
  60. package/src/http_server.ts +6 -0
  61. package/src/index.ts +3 -1
  62. package/src/ipc/job_main.ts +9 -2
  63. package/src/ipc/proc_pool.ts +1 -0
  64. package/src/job.ts +37 -1
  65. package/src/multimodal/agent_playout.ts +254 -0
  66. package/src/multimodal/index.ts +5 -0
  67. package/src/multimodal/multimodal_agent.ts +426 -0
  68. package/src/transcription.ts +129 -0
  69. package/src/utils.ts +151 -12
  70. package/src/worker.ts +60 -14
  71. package/tsconfig.json +1 -1
package/src/utils.ts CHANGED
@@ -116,7 +116,8 @@ export class Mutex {
116
116
 
117
117
  /** @internal */
118
118
  export class Queue<T> {
119
- #items: T[] = [];
119
+ /** @internal */
120
+ items: T[] = [];
120
121
  #limit?: number;
121
122
  #events = new EventEmitter();
122
123
 
@@ -125,35 +126,173 @@ export class Queue<T> {
125
126
  }
126
127
 
127
128
  async get(): Promise<T> {
128
- if (this.#items.length === 0) {
129
+ if (this.items.length === 0) {
129
130
  await once(this.#events, 'put');
130
131
  }
131
- const item = this.#items.shift()!;
132
+ const item = this.items.shift()!;
132
133
  this.#events.emit('get');
133
134
  return item;
134
135
  }
135
136
 
136
137
  async put(item: T) {
137
- if (this.#limit && this.#items.length >= this.#limit) {
138
+ if (this.#limit && this.items.length >= this.#limit) {
138
139
  await once(this.#events, 'get');
139
140
  }
140
- this.#items.push(item);
141
+ this.items.push(item);
141
142
  this.#events.emit('put');
142
143
  }
143
144
  }
144
145
 
145
146
  /** @internal */
146
147
  export class Future {
147
- #await = new Promise<void>((resolve, reject: (_: Error) => void) => {
148
- this.resolve = resolve;
149
- this.reject = reject;
150
- });
148
+ #await: Promise<void>;
149
+ #resolvePromise!: () => void;
150
+ #rejectPromise!: (error: Error) => void;
151
+ #done: boolean = false;
152
+
153
+ constructor() {
154
+ this.#await = new Promise<void>((resolve, reject) => {
155
+ this.#resolvePromise = resolve;
156
+ this.#rejectPromise = reject;
157
+ });
158
+ }
151
159
 
152
160
  get await() {
153
161
  return this.#await;
154
162
  }
155
- resolve() {}
156
- reject(_: Error) {
157
- _;
163
+
164
+ get done() {
165
+ return this.#done;
166
+ }
167
+
168
+ resolve() {
169
+ this.#done = true;
170
+ this.#resolvePromise();
171
+ }
172
+
173
+ reject(error: Error) {
174
+ this.#done = true;
175
+ this.#rejectPromise(error);
176
+ }
177
+ }
178
+
179
+ /** @internal */
180
+ export class CancellablePromise<T> {
181
+ #promise: Promise<T>;
182
+ #cancelFn: () => void;
183
+ #isCancelled: boolean = false;
184
+ #error: Error | null = null;
185
+
186
+ constructor(
187
+ executor: (
188
+ resolve: (value: T | PromiseLike<T>) => void,
189
+ reject: (reason?: any) => void,
190
+ onCancel: (cancelFn: () => void) => void,
191
+ ) => void,
192
+ ) {
193
+ let cancel: () => void;
194
+
195
+ this.#promise = new Promise<T>((resolve, reject) => {
196
+ executor(
197
+ resolve,
198
+ (reason) => {
199
+ this.#error = reason instanceof Error ? reason : new Error(String(reason));
200
+ reject(reason);
201
+ },
202
+ (cancelFn) => {
203
+ cancel = () => {
204
+ this.#isCancelled = true;
205
+ cancelFn();
206
+ };
207
+ },
208
+ );
209
+ });
210
+
211
+ this.#cancelFn = cancel!;
212
+ }
213
+
214
+ get isCancelled(): boolean {
215
+ return this.#isCancelled;
216
+ }
217
+
218
+ get error(): Error | null {
219
+ return this.#error;
220
+ }
221
+
222
+ then<TResult1 = T, TResult2 = never>(
223
+ onfulfilled?: ((value: T) => TResult1 | Promise<TResult1>) | null,
224
+ onrejected?: ((reason: any) => TResult2 | Promise<TResult2>) | null,
225
+ ): Promise<TResult1 | TResult2> {
226
+ return this.#promise.then(onfulfilled, onrejected);
227
+ }
228
+
229
+ catch<TResult = never>(
230
+ onrejected?: ((reason: any) => TResult | Promise<TResult>) | null,
231
+ ): Promise<T | TResult> {
232
+ return this.#promise.catch(onrejected);
233
+ }
234
+
235
+ finally(onfinally?: (() => void) | null): Promise<T> {
236
+ return this.#promise.finally(onfinally);
237
+ }
238
+
239
+ cancel(): void {
240
+ this.#cancelFn();
241
+ }
242
+
243
+ static from<T>(promise: Promise<T>): CancellablePromise<T> {
244
+ return new CancellablePromise<T>((resolve, reject) => {
245
+ promise.then(resolve).catch(reject);
246
+ });
247
+ }
248
+ }
249
+
250
+ /** @internal */
251
+ export async function gracefullyCancel<T>(promise: CancellablePromise<T>): Promise<void> {
252
+ if (!promise.isCancelled) {
253
+ promise.cancel();
254
+ }
255
+ try {
256
+ await promise;
257
+ } catch (error) {
258
+ // Ignore the error, as it's expected due to cancellation
259
+ }
260
+ }
261
+
262
+ /** @internal */
263
+ export class AsyncIterableQueue<T> implements AsyncIterable<T> {
264
+ private queue: Queue<T | typeof AsyncIterableQueue.QUEUE_END_MARKER>;
265
+ private closed = false;
266
+ private static readonly QUEUE_END_MARKER = Symbol('QUEUE_END_MARKER');
267
+
268
+ constructor() {
269
+ this.queue = new Queue<T | typeof AsyncIterableQueue.QUEUE_END_MARKER>();
270
+ }
271
+
272
+ put(item: T): void {
273
+ if (this.closed) {
274
+ throw new Error('Queue is closed');
275
+ }
276
+ this.queue.put(item);
277
+ }
278
+
279
+ close(): void {
280
+ this.closed = true;
281
+ this.queue.put(AsyncIterableQueue.QUEUE_END_MARKER);
282
+ }
283
+
284
+ [Symbol.asyncIterator](): AsyncIterator<T> {
285
+ return {
286
+ next: async (): Promise<IteratorResult<T>> => {
287
+ if (this.closed && this.queue.items.length === 0) {
288
+ return { value: undefined, done: true };
289
+ }
290
+ const item = await this.queue.get();
291
+ if (item === AsyncIterableQueue.QUEUE_END_MARKER && this.closed) {
292
+ return { value: undefined, done: true };
293
+ }
294
+ return { value: item as T, done: false };
295
+ },
296
+ };
158
297
  }
159
298
  }
package/src/worker.ts CHANGED
@@ -31,6 +31,32 @@ const MAX_RECONNECT_ATTEMPTS = 10;
31
31
  const ASSIGNMENT_TIMEOUT = 7.5 * 1000;
32
32
  const UPDATE_LOAD_INTERVAL = 2.5 * 1000;
33
33
 
34
+ class Default {
35
+ static loadThreshold(production: boolean): number {
36
+ if (production) {
37
+ return 0.65;
38
+ } else {
39
+ return Infinity;
40
+ }
41
+ }
42
+
43
+ static numIdleProcesses(production: boolean): number {
44
+ if (production) {
45
+ return 3;
46
+ } else {
47
+ return 0;
48
+ }
49
+ }
50
+
51
+ static port(production: boolean): number {
52
+ if (production) {
53
+ return 8081;
54
+ } else {
55
+ return 0;
56
+ }
57
+ }
58
+ }
59
+
34
60
  /** Necessary credentials not provided and not found in an appropriate environmental variable. */
35
61
  export class MissingCredentialsError extends Error {
36
62
  constructor(msg?: string) {
@@ -132,14 +158,15 @@ export class WorkerOptions {
132
158
  host: string;
133
159
  port: number;
134
160
  logLevel: string;
161
+ production: boolean;
135
162
 
136
163
  /** @param options */
137
164
  constructor({
138
165
  agent,
139
166
  requestFunc = defaultRequestFunc,
140
167
  loadFunc = defaultCpuLoad,
141
- loadThreshold = 0.65,
142
- numIdleProcesses = 3,
168
+ loadThreshold = undefined,
169
+ numIdleProcesses = undefined,
143
170
  shutdownProcessTimeout = 60 * 1000,
144
171
  initializeProcessTimeout = 10 * 1000,
145
172
  permissions = new WorkerPermissions(),
@@ -150,8 +177,9 @@ export class WorkerOptions {
150
177
  apiKey = undefined,
151
178
  apiSecret = undefined,
152
179
  host = 'localhost',
153
- port = 8081,
180
+ port = undefined,
154
181
  logLevel = 'info',
182
+ production = false,
155
183
  }: {
156
184
  /**
157
185
  * Path to a file that has {@link Agent} as a default export, dynamically imported later for
@@ -176,12 +204,16 @@ export class WorkerOptions {
176
204
  host?: string;
177
205
  port?: number;
178
206
  logLevel?: string;
207
+ production?: boolean;
179
208
  }) {
180
209
  this.agent = agent;
210
+ if (!this.agent) {
211
+ throw new Error('No Agent file was passed to the worker');
212
+ }
181
213
  this.requestFunc = requestFunc;
182
214
  this.loadFunc = loadFunc;
183
- this.loadThreshold = loadThreshold;
184
- this.numIdleProcesses = numIdleProcesses;
215
+ this.loadThreshold = loadThreshold || Default.loadThreshold(production);
216
+ this.numIdleProcesses = numIdleProcesses || Default.numIdleProcesses(production);
185
217
  this.shutdownProcessTimeout = shutdownProcessTimeout;
186
218
  this.initializeProcessTimeout = initializeProcessTimeout;
187
219
  this.permissions = permissions;
@@ -192,8 +224,9 @@ export class WorkerOptions {
192
224
  this.apiKey = apiKey;
193
225
  this.apiSecret = apiSecret;
194
226
  this.host = host;
195
- this.port = port;
227
+ this.port = port || Default.port(production);
196
228
  this.logLevel = logLevel;
229
+ this.production = production;
197
230
  }
198
231
  }
199
232
 
@@ -239,12 +272,16 @@ export class Worker {
239
272
  opts.apiSecret = opts.apiSecret || process.env.LIVEKIT_API_SECRET || '';
240
273
 
241
274
  if (opts.wsURL === '')
242
- throw new MissingCredentialsError('--url is required, or set LIVEKIT_URL env var');
275
+ throw new MissingCredentialsError(
276
+ 'URL is required: Set LIVEKIT_URL, run with --url, or pass wsURL in WorkerOptions',
277
+ );
243
278
  if (opts.apiKey === '')
244
- throw new MissingCredentialsError('--api-key is required, or set LIVEKIT_API_KEY env var');
279
+ throw new MissingCredentialsError(
280
+ 'API Key is required: Set LIVEKIT_API_KEY, run with --api-key, or pass apiKey in WorkerOptions',
281
+ );
245
282
  if (opts.apiSecret === '')
246
283
  throw new MissingCredentialsError(
247
- '--api-secret is required, or set LIVEKIT_API_SECRET env var',
284
+ 'API Secret is required: Set LIVEKIT_API_SECRET, run with --api-secret, or pass apiSecret in WorkerOptions',
248
285
  );
249
286
 
250
287
  this.#procPool = new ProcPool(
@@ -286,7 +323,7 @@ export class Worker {
286
323
  await new Promise((resolve, reject) => {
287
324
  this.#session!.on('open', resolve);
288
325
  this.#session!.on('error', (error) => reject(error));
289
- this.#session!.on('close', (code) => reject(`WebSocket returned ${code}`));
326
+ this.#session!.on('close', (code) => reject(new Error(`WebSocket returned ${code}`)));
290
327
  });
291
328
 
292
329
  retries = 0;
@@ -373,10 +410,17 @@ export class Worker {
373
410
  const room = await client.createRoom({ name: roomName });
374
411
  let participant: ParticipantInfo | undefined = undefined;
375
412
  if (participantIdentity) {
376
- participant = await client.getParticipant(roomName, participantIdentity);
413
+ try {
414
+ participant = await client.getParticipant(roomName, participantIdentity);
415
+ } catch (e) {
416
+ this.#logger.fatal(
417
+ `participant with identity ${participantIdentity} not found in room ${roomName}`,
418
+ );
419
+ throw e;
420
+ }
377
421
  }
378
422
 
379
- this.event!.emit(
423
+ this.event.emit(
380
424
  'worker_msg',
381
425
  new WorkerMessage({
382
426
  message: {
@@ -433,19 +477,21 @@ export class Worker {
433
477
  this.event.emit(
434
478
  'worker_registered',
435
479
  msg.message.value.workerId,
436
- msg.message.value.serverInfo!,
480
+ msg.message.value.serverInfo,
437
481
  );
438
482
  this.#connecting = false;
439
483
  break;
440
484
  }
441
485
  case 'availability': {
486
+ if (!msg.message.value.job) return;
442
487
  const task = this.#availability(msg.message.value);
443
488
  this.#tasks.push(task);
444
489
  task.finally(() => this.#tasks.splice(this.#tasks.indexOf(task)));
445
490
  break;
446
491
  }
447
492
  case 'assignment': {
448
- const job = msg.message.value.job!;
493
+ if (!msg.message.value.job) return;
494
+ const job = msg.message.value.job;
449
495
  if (job.id in this.#pending) {
450
496
  const task = this.#pending[job.id];
451
497
  delete this.#pending[job.id];
package/tsconfig.json CHANGED
@@ -5,7 +5,7 @@
5
5
  // match output dir to input dir. e.g. dist/index instead of dist/src/index
6
6
  "rootDir": "./src",
7
7
  "declarationDir": "./dist",
8
- "outDir": "./dist"
8
+ "outDir": "./dist",
9
9
  },
10
10
  "typedocOptions": {
11
11
  "name": "agents",