@livekit/agents 0.2.0 → 0.3.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 (70) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/dist/audio.d.ts +1 -4
  4. package/dist/audio.d.ts.map +1 -1
  5. package/dist/audio.js +28 -11
  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 +36 -13
  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.map +1 -1
  16. package/dist/http_server.js +5 -0
  17. package/dist/http_server.js.map +1 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +3 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/ipc/job_main.js +9 -1
  23. package/dist/ipc/job_main.js.map +1 -1
  24. package/dist/ipc/proc_pool.d.ts.map +1 -1
  25. package/dist/ipc/proc_pool.js +1 -0
  26. package/dist/ipc/proc_pool.js.map +1 -1
  27. package/dist/job.d.ts +1 -0
  28. package/dist/job.d.ts.map +1 -1
  29. package/dist/job.js +17 -1
  30. package/dist/job.js.map +1 -1
  31. package/dist/multimodal/agent_playout.d.ts +34 -0
  32. package/dist/multimodal/agent_playout.d.ts.map +1 -0
  33. package/dist/multimodal/agent_playout.js +221 -0
  34. package/dist/multimodal/agent_playout.js.map +1 -0
  35. package/dist/multimodal/index.d.ts +3 -0
  36. package/dist/multimodal/index.d.ts.map +1 -0
  37. package/dist/multimodal/index.js +6 -0
  38. package/dist/multimodal/index.js.map +1 -0
  39. package/dist/multimodal/multimodal_agent.d.ts +47 -0
  40. package/dist/multimodal/multimodal_agent.d.ts.map +1 -0
  41. package/dist/multimodal/multimodal_agent.js +331 -0
  42. package/dist/multimodal/multimodal_agent.js.map +1 -0
  43. package/dist/transcription.d.ts +22 -0
  44. package/dist/transcription.d.ts.map +1 -0
  45. package/dist/transcription.js +111 -0
  46. package/dist/transcription.js.map +1 -0
  47. package/dist/utils.d.ts +27 -0
  48. package/dist/utils.d.ts.map +1 -1
  49. package/dist/utils.js +107 -9
  50. package/dist/utils.js.map +1 -1
  51. package/dist/worker.d.ts +3 -1
  52. package/dist/worker.d.ts.map +1 -1
  53. package/dist/worker.js +44 -8
  54. package/dist/worker.js.map +1 -1
  55. package/package.json +6 -4
  56. package/src/audio.ts +19 -19
  57. package/src/cli.ts +37 -13
  58. package/src/generator.ts +14 -0
  59. package/src/http_server.ts +5 -0
  60. package/src/index.ts +3 -1
  61. package/src/ipc/job_main.ts +9 -2
  62. package/src/ipc/proc_pool.ts +1 -0
  63. package/src/job.ts +21 -1
  64. package/src/multimodal/agent_playout.ts +254 -0
  65. package/src/multimodal/index.ts +5 -0
  66. package/src/multimodal/multimodal_agent.ts +428 -0
  67. package/src/transcription.ts +128 -0
  68. package/src/utils.ts +138 -6
  69. package/src/worker.ts +54 -10
  70. 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,19 +126,19 @@ 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
  }
@@ -148,12 +149,143 @@ export class Future {
148
149
  this.resolve = resolve;
149
150
  this.reject = reject;
150
151
  });
152
+ #done: boolean = false;
151
153
 
152
154
  get await() {
153
155
  return this.#await;
154
156
  }
155
- resolve() {}
157
+
158
+ get done() {
159
+ return this.#done;
160
+ }
161
+
162
+ resolve() {
163
+ this.#done = true;
164
+ }
165
+
156
166
  reject(_: Error) {
167
+ this.#done = true;
157
168
  _;
158
169
  }
159
170
  }
171
+
172
+ /** @internal */
173
+ export class CancellablePromise<T> {
174
+ #promise: Promise<T>;
175
+ #cancelFn: () => void;
176
+ #isCancelled: boolean = false;
177
+ #error: Error | null = null;
178
+
179
+ constructor(
180
+ executor: (
181
+ resolve: (value: T | PromiseLike<T>) => void,
182
+ reject: (reason?: any) => void,
183
+ onCancel: (cancelFn: () => void) => void,
184
+ ) => void,
185
+ ) {
186
+ let cancel: () => void;
187
+
188
+ this.#promise = new Promise<T>((resolve, reject) => {
189
+ executor(
190
+ resolve,
191
+ (reason) => {
192
+ this.#error = reason instanceof Error ? reason : new Error(String(reason));
193
+ reject(reason);
194
+ },
195
+ (cancelFn) => {
196
+ cancel = () => {
197
+ this.#isCancelled = true;
198
+ cancelFn();
199
+ };
200
+ },
201
+ );
202
+ });
203
+
204
+ this.#cancelFn = cancel!;
205
+ }
206
+
207
+ get isCancelled(): boolean {
208
+ return this.#isCancelled;
209
+ }
210
+
211
+ get error(): Error | null {
212
+ return this.#error;
213
+ }
214
+
215
+ then<TResult1 = T, TResult2 = never>(
216
+ onfulfilled?: ((value: T) => TResult1 | Promise<TResult1>) | null,
217
+ onrejected?: ((reason: any) => TResult2 | Promise<TResult2>) | null,
218
+ ): Promise<TResult1 | TResult2> {
219
+ return this.#promise.then(onfulfilled, onrejected);
220
+ }
221
+
222
+ catch<TResult = never>(
223
+ onrejected?: ((reason: any) => TResult | Promise<TResult>) | null,
224
+ ): Promise<T | TResult> {
225
+ return this.#promise.catch(onrejected);
226
+ }
227
+
228
+ finally(onfinally?: (() => void) | null): Promise<T> {
229
+ return this.#promise.finally(onfinally);
230
+ }
231
+
232
+ cancel(): void {
233
+ this.#cancelFn();
234
+ }
235
+
236
+ static from<T>(promise: Promise<T>): CancellablePromise<T> {
237
+ return new CancellablePromise<T>((resolve, reject) => {
238
+ promise.then(resolve).catch(reject);
239
+ });
240
+ }
241
+ }
242
+
243
+ /** @internal */
244
+ export async function gracefullyCancel<T>(promise: CancellablePromise<T>): Promise<void> {
245
+ if (!promise.isCancelled) {
246
+ promise.cancel();
247
+ }
248
+ try {
249
+ await promise;
250
+ } catch (error) {
251
+ // Ignore the error, as it's expected due to cancellation
252
+ }
253
+ }
254
+
255
+ /** @internal */
256
+ export class AsyncIterableQueue<T> implements AsyncIterable<T> {
257
+ private queue: Queue<T | typeof AsyncIterableQueue.QUEUE_END_MARKER>;
258
+ private closed = false;
259
+ private static readonly QUEUE_END_MARKER = Symbol('QUEUE_END_MARKER');
260
+
261
+ constructor() {
262
+ this.queue = new Queue<T | typeof AsyncIterableQueue.QUEUE_END_MARKER>();
263
+ }
264
+
265
+ put(item: T): void {
266
+ if (this.closed) {
267
+ throw new Error('Queue is closed');
268
+ }
269
+ this.queue.put(item);
270
+ }
271
+
272
+ close(): void {
273
+ this.closed = true;
274
+ this.queue.put(AsyncIterableQueue.QUEUE_END_MARKER);
275
+ }
276
+
277
+ [Symbol.asyncIterator](): AsyncIterator<T> {
278
+ return {
279
+ next: async (): Promise<IteratorResult<T>> => {
280
+ if (this.closed && this.queue.items.length === 0) {
281
+ return { value: undefined, done: true };
282
+ }
283
+ const item = await this.queue.get();
284
+ if (item === AsyncIterableQueue.QUEUE_END_MARKER && this.closed) {
285
+ return { value: undefined, done: true };
286
+ }
287
+ return { value: item as T, done: false };
288
+ },
289
+ };
290
+ }
291
+ }
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(
@@ -373,7 +410,14 @@ 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
423
  this.event!.emit(
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",