@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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +40 -0
- package/dist/audio.d.ts +1 -4
- package/dist/audio.d.ts.map +1 -1
- package/dist/audio.js +30 -12
- package/dist/audio.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +41 -17
- package/dist/cli.js.map +1 -1
- package/dist/generator.d.ts +5 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +11 -0
- package/dist/generator.js.map +1 -1
- package/dist/http_server.d.ts +1 -0
- package/dist/http_server.d.ts.map +1 -1
- package/dist/http_server.js +13 -0
- package/dist/http_server.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ipc/job_main.js +9 -1
- package/dist/ipc/job_main.js.map +1 -1
- package/dist/ipc/proc_pool.d.ts.map +1 -1
- package/dist/ipc/proc_pool.js +1 -0
- package/dist/ipc/proc_pool.js.map +1 -1
- package/dist/job.d.ts +1 -0
- package/dist/job.d.ts.map +1 -1
- package/dist/job.js +30 -1
- package/dist/job.js.map +1 -1
- package/dist/multimodal/agent_playout.d.ts +34 -0
- package/dist/multimodal/agent_playout.d.ts.map +1 -0
- package/dist/multimodal/agent_playout.js +221 -0
- package/dist/multimodal/agent_playout.js.map +1 -0
- package/dist/multimodal/index.d.ts +3 -0
- package/dist/multimodal/index.d.ts.map +1 -0
- package/dist/multimodal/index.js +6 -0
- package/dist/multimodal/index.js.map +1 -0
- package/dist/multimodal/multimodal_agent.d.ts +47 -0
- package/dist/multimodal/multimodal_agent.d.ts.map +1 -0
- package/dist/multimodal/multimodal_agent.js +329 -0
- package/dist/multimodal/multimodal_agent.js.map +1 -0
- package/dist/transcription.d.ts +22 -0
- package/dist/transcription.d.ts.map +1 -0
- package/dist/transcription.js +112 -0
- package/dist/transcription.js.map +1 -0
- package/dist/utils.d.ts +29 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +117 -15
- package/dist/utils.js.map +1 -1
- package/dist/worker.d.ts +3 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +49 -9
- package/dist/worker.js.map +1 -1
- package/package.json +6 -4
- package/src/audio.ts +21 -20
- package/src/cli.ts +42 -17
- package/src/generator.ts +14 -0
- package/src/http_server.ts +6 -0
- package/src/index.ts +3 -1
- package/src/ipc/job_main.ts +9 -2
- package/src/ipc/proc_pool.ts +1 -0
- package/src/job.ts +37 -1
- package/src/multimodal/agent_playout.ts +254 -0
- package/src/multimodal/index.ts +5 -0
- package/src/multimodal/multimodal_agent.ts +426 -0
- package/src/transcription.ts +129 -0
- package/src/utils.ts +151 -12
- package/src/worker.ts +60 -14
- 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
|
-
|
|
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
|
|
129
|
+
if (this.items.length === 0) {
|
|
129
130
|
await once(this.#events, 'put');
|
|
130
131
|
}
|
|
131
|
-
const item = this
|
|
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
|
|
138
|
+
if (this.#limit && this.items.length >= this.#limit) {
|
|
138
139
|
await once(this.#events, 'get');
|
|
139
140
|
}
|
|
140
|
-
this
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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 =
|
|
142
|
-
numIdleProcesses =
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
'
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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];
|