@livekit/agents 1.2.0 → 1.2.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 (101) hide show
  1. package/dist/_exceptions.cjs.map +1 -1
  2. package/dist/_exceptions.d.ts.map +1 -1
  3. package/dist/_exceptions.js.map +1 -1
  4. package/dist/beta/workflows/task_group.cjs +7 -4
  5. package/dist/beta/workflows/task_group.cjs.map +1 -1
  6. package/dist/beta/workflows/task_group.d.ts.map +1 -1
  7. package/dist/beta/workflows/task_group.js +7 -4
  8. package/dist/beta/workflows/task_group.js.map +1 -1
  9. package/dist/inference/interruption/http_transport.cjs.map +1 -1
  10. package/dist/inference/interruption/http_transport.d.cts +3 -1
  11. package/dist/inference/interruption/http_transport.d.ts +3 -1
  12. package/dist/inference/interruption/http_transport.d.ts.map +1 -1
  13. package/dist/inference/interruption/http_transport.js.map +1 -1
  14. package/dist/inference/interruption/ws_transport.cjs +37 -32
  15. package/dist/inference/interruption/ws_transport.cjs.map +1 -1
  16. package/dist/inference/interruption/ws_transport.d.ts.map +1 -1
  17. package/dist/inference/interruption/ws_transport.js +37 -32
  18. package/dist/inference/interruption/ws_transport.js.map +1 -1
  19. package/dist/inference/tts.cjs.map +1 -1
  20. package/dist/inference/tts.d.cts +42 -4
  21. package/dist/inference/tts.d.ts +42 -4
  22. package/dist/inference/tts.d.ts.map +1 -1
  23. package/dist/inference/tts.js.map +1 -1
  24. package/dist/inference/tts.test.cjs +72 -0
  25. package/dist/inference/tts.test.cjs.map +1 -1
  26. package/dist/inference/tts.test.js +72 -0
  27. package/dist/inference/tts.test.js.map +1 -1
  28. package/dist/llm/chat_context.cjs +102 -31
  29. package/dist/llm/chat_context.cjs.map +1 -1
  30. package/dist/llm/chat_context.d.ts.map +1 -1
  31. package/dist/llm/chat_context.js +102 -31
  32. package/dist/llm/chat_context.js.map +1 -1
  33. package/dist/llm/chat_context.test.cjs +123 -5
  34. package/dist/llm/chat_context.test.cjs.map +1 -1
  35. package/dist/llm/chat_context.test.js +123 -5
  36. package/dist/llm/chat_context.test.js.map +1 -1
  37. package/dist/llm/fallback_adapter.cjs +2 -0
  38. package/dist/llm/fallback_adapter.cjs.map +1 -1
  39. package/dist/llm/fallback_adapter.d.ts.map +1 -1
  40. package/dist/llm/fallback_adapter.js +2 -0
  41. package/dist/llm/fallback_adapter.js.map +1 -1
  42. package/dist/llm/index.cjs +2 -0
  43. package/dist/llm/index.cjs.map +1 -1
  44. package/dist/llm/index.d.cts +1 -1
  45. package/dist/llm/index.d.ts +1 -1
  46. package/dist/llm/index.d.ts.map +1 -1
  47. package/dist/llm/index.js +2 -0
  48. package/dist/llm/index.js.map +1 -1
  49. package/dist/llm/utils.cjs +89 -0
  50. package/dist/llm/utils.cjs.map +1 -1
  51. package/dist/llm/utils.d.cts +8 -0
  52. package/dist/llm/utils.d.ts +8 -0
  53. package/dist/llm/utils.d.ts.map +1 -1
  54. package/dist/llm/utils.js +88 -0
  55. package/dist/llm/utils.js.map +1 -1
  56. package/dist/llm/utils.test.cjs +90 -0
  57. package/dist/llm/utils.test.cjs.map +1 -1
  58. package/dist/llm/utils.test.js +98 -2
  59. package/dist/llm/utils.test.js.map +1 -1
  60. package/dist/stt/stt.cjs +8 -0
  61. package/dist/stt/stt.cjs.map +1 -1
  62. package/dist/stt/stt.d.cts +8 -0
  63. package/dist/stt/stt.d.ts +8 -0
  64. package/dist/stt/stt.d.ts.map +1 -1
  65. package/dist/stt/stt.js +8 -0
  66. package/dist/stt/stt.js.map +1 -1
  67. package/dist/tts/fallback_adapter.cjs +6 -0
  68. package/dist/tts/fallback_adapter.cjs.map +1 -1
  69. package/dist/tts/fallback_adapter.d.ts.map +1 -1
  70. package/dist/tts/fallback_adapter.js +6 -0
  71. package/dist/tts/fallback_adapter.js.map +1 -1
  72. package/dist/typed_promise.cjs +48 -0
  73. package/dist/typed_promise.cjs.map +1 -0
  74. package/dist/typed_promise.d.cts +24 -0
  75. package/dist/typed_promise.d.ts +24 -0
  76. package/dist/typed_promise.d.ts.map +1 -0
  77. package/dist/typed_promise.js +28 -0
  78. package/dist/typed_promise.js.map +1 -0
  79. package/dist/utils.cjs +2 -2
  80. package/dist/utils.cjs.map +1 -1
  81. package/dist/utils.js +2 -2
  82. package/dist/utils.js.map +1 -1
  83. package/dist/version.cjs +1 -1
  84. package/dist/version.js +1 -1
  85. package/package.json +4 -2
  86. package/src/_exceptions.ts +5 -0
  87. package/src/beta/workflows/task_group.ts +14 -5
  88. package/src/inference/interruption/http_transport.ts +2 -1
  89. package/src/inference/interruption/ws_transport.ts +44 -34
  90. package/src/inference/tts.test.ts +87 -0
  91. package/src/inference/tts.ts +46 -6
  92. package/src/llm/chat_context.test.ts +137 -5
  93. package/src/llm/chat_context.ts +119 -38
  94. package/src/llm/fallback_adapter.ts +5 -2
  95. package/src/llm/index.ts +2 -0
  96. package/src/llm/utils.test.ts +103 -2
  97. package/src/llm/utils.ts +128 -0
  98. package/src/stt/stt.ts +9 -1
  99. package/src/tts/fallback_adapter.ts +9 -2
  100. package/src/typed_promise.ts +67 -0
  101. package/src/utils.ts +2 -2
package/src/llm/utils.ts CHANGED
@@ -7,6 +7,7 @@ import sharp from 'sharp';
7
7
  import type { UnknownUserData } from '../voice/run_context.js';
8
8
  import type { ChatContext } from './chat_context.js';
9
9
  import {
10
+ type ChatContent,
10
11
  type ChatItem,
11
12
  FunctionCall,
12
13
  FunctionCallOutput,
@@ -241,6 +242,133 @@ export async function executeToolCall(
241
242
  }
242
243
  }
243
244
 
245
+ export interface FormatChatHistoryOptions {
246
+ includeIds?: boolean;
247
+ includeTimestamps?: boolean;
248
+ }
249
+
250
+ /**
251
+ * Render a chat context into a readable multiline string for debugging and logging.
252
+ */
253
+ export function formatChatHistory(
254
+ chatCtx: ChatContext,
255
+ options: FormatChatHistoryOptions = {},
256
+ ): string {
257
+ const { includeIds = false, includeTimestamps = false } = options;
258
+
259
+ if (chatCtx.items.length === 0) {
260
+ return 'Chat history (0 items)';
261
+ }
262
+
263
+ const formattedItems = chatCtx.items.map((item, index) =>
264
+ formatChatHistoryItem(item, index, {
265
+ includeIds,
266
+ includeTimestamps,
267
+ }),
268
+ );
269
+
270
+ return [
271
+ `Chat history (${chatCtx.items.length} items)`,
272
+ ...formattedItems.flatMap((item) => ['', item]),
273
+ ].join('\n');
274
+ }
275
+
276
+ function formatChatHistoryItem(
277
+ item: ChatItem,
278
+ index: number,
279
+ options: Required<FormatChatHistoryOptions>,
280
+ ): string {
281
+ const headerParts = [`[${index}]`];
282
+
283
+ if (item.type === 'message') {
284
+ headerParts.push('message', item.role);
285
+ } else if (item.type === 'function_call') {
286
+ headerParts.push('function_call', item.name, `call_id=${item.callId}`);
287
+ } else if (item.type === 'function_call_output') {
288
+ headerParts.push('function_call_output', item.name || '(unnamed)', `call_id=${item.callId}`);
289
+ if (item.isError) {
290
+ headerParts.push('error=true');
291
+ }
292
+ } else {
293
+ headerParts.push('agent_handoff');
294
+ }
295
+
296
+ if (options.includeIds) {
297
+ headerParts.push(`id=${item.id}`);
298
+ }
299
+
300
+ if (options.includeTimestamps) {
301
+ headerParts.push(`created_at=${item.createdAt.toFixed(3)}`);
302
+ }
303
+
304
+ const body = formatChatHistoryItemBody(item);
305
+ if (!body) {
306
+ return headerParts.join(' ');
307
+ }
308
+
309
+ return `${headerParts.join(' ')}\n${indentBlock(body, ' ')}`;
310
+ }
311
+
312
+ function formatChatHistoryItemBody(item: ChatItem): string {
313
+ if (item.type === 'message') {
314
+ const content = item.content.map((part) => formatMessageContentPart(part)).join('\n');
315
+ return content.trim() ? content : '(empty)';
316
+ }
317
+
318
+ if (item.type === 'function_call') {
319
+ return prettyJsonText(item.args);
320
+ }
321
+
322
+ if (item.type === 'function_call_output') {
323
+ return prettyJsonText(item.output);
324
+ }
325
+
326
+ return `${item.oldAgentId ?? '(none)'} -> ${item.newAgentId}`;
327
+ }
328
+
329
+ function formatMessageContentPart(part: ChatContent): string {
330
+ if (typeof part === 'string') {
331
+ return part;
332
+ }
333
+
334
+ if (part.type === 'image_content') {
335
+ if (typeof part.image === 'string') {
336
+ return `[image url=${truncateText(part.image, 120)}]`;
337
+ }
338
+
339
+ return `[image frame=${part.image.width}x${part.image.height}]`;
340
+ }
341
+
342
+ if (part.transcript) {
343
+ return `[audio transcript=${JSON.stringify(truncateText(part.transcript, 120))}]`;
344
+ }
345
+
346
+ return `[audio frames=${part.frame.length}]`;
347
+ }
348
+
349
+ function prettyJsonText(text: string): string {
350
+ try {
351
+ return JSON.stringify(JSON.parse(text), null, 2);
352
+ } catch {
353
+ return text;
354
+ }
355
+ }
356
+
357
+ function truncateText(text: string, maxLength: number): string {
358
+ if (text.length <= maxLength) {
359
+ return text;
360
+ }
361
+
362
+ return `${text.slice(0, Math.max(0, maxLength - 3))}...`;
363
+ }
364
+
365
+ function indentBlock(text: string, indent: string): string {
366
+ return text
367
+ .split('\n')
368
+ .map((line) => `${indent}${line}`)
369
+ .join('\n');
370
+ }
371
+
244
372
  /**
245
373
  * Standard dynamic-programming LCS to get the common subsequence
246
374
  * of IDs (in order) that appear in both old_ids and new_ids.
package/src/stt/stt.ts CHANGED
@@ -248,7 +248,15 @@ export abstract class SpeechStream implements AsyncIterableIterator<SpeechEvent>
248
248
  startSoon(() => this.mainTask().finally(() => this.queue.close()));
249
249
  }
250
250
 
251
- private async mainTask() {
251
+ /**
252
+ * Runs the STT with retry logic. Errors are emitted via {@link STT} error events
253
+ * and then re-thrown to trigger `.finally()` cleanup.
254
+ *
255
+ * @throws {APIError} When the STT request fails with a non-retryable error
256
+ * @throws {APIConnectionError} When all retry attempts are exhausted
257
+ * @internal Not annotated with Throws<> because this is fire-and-forget via startSoon()
258
+ */
259
+ private async mainTask(): Promise<void> {
252
260
  for (let i = 0; i < this._connOptions.maxRetry + 1; i++) {
253
261
  try {
254
262
  return await this.run();
@@ -2,6 +2,7 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import { AudioResampler } from '@livekit/rtc-node';
5
+ import type { Throws } from '@livekit/throws-transformer/throws';
5
6
  import { APIConnectionError, APIError } from '../_exceptions.js';
6
7
  import { log } from '../log.js';
7
8
  import { basic } from '../tokenize/index.js';
@@ -306,7 +307,10 @@ class FallbackChunkedStream extends ChunkedStream {
306
307
  this.connOptions = connOptions;
307
308
  }
308
309
 
309
- protected async run(): Promise<void> {
310
+ /**
311
+ * @throws {APIConnectionError} When all TTS providers have been exhausted
312
+ */
313
+ protected async run(): Promise<Throws<void, APIConnectionError>> {
310
314
  const allTTSFailed = this.adapter.status.every((s) => !s.available);
311
315
  let lastRequestId: string = '';
312
316
  let lastSegmentId: string = '';
@@ -406,7 +410,10 @@ class FallbackSynthesizeStream extends SynthesizeStream {
406
410
  this.adapter = adapter;
407
411
  }
408
412
 
409
- protected async run(): Promise<void> {
413
+ /**
414
+ * @throws {APIConnectionError} When all TTS providers have been exhausted
415
+ */
416
+ protected async run(): Promise<Throws<void, APIConnectionError>> {
410
417
  const allTTSFailed = this.adapter.status.every((s) => !s.available);
411
418
  if (allTTSFailed) {
412
419
  this._logger.warn('All fallback TTS instances failed, retrying from first...');
@@ -0,0 +1,67 @@
1
+ // SPDX-FileCopyrightText: 2026 LiveKit, Inc.
2
+ //
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ type InferErrors<T> = T extends TypedPromise<any, infer E> ? E : never;
5
+
6
+ interface PromiseRejectedResult<E> {
7
+ status: 'rejected';
8
+ reason: E;
9
+ }
10
+
11
+ type SettledResult<T> =
12
+ T extends TypedPromise<infer U, infer E>
13
+ ? PromiseFulfilledResult<U> | PromiseRejectedResult<E>
14
+ : T extends PromiseLike<infer U>
15
+ ? PromiseFulfilledResult<U> | PromiseRejectedResult<unknown>
16
+ : PromiseFulfilledResult<T> | PromiseRejectedResult<unknown>;
17
+
18
+ export default class TypedPromise<T, E extends Error> extends Promise<T> {
19
+ // eslint-disable-next-line @typescript-eslint/no-useless-constructor
20
+ constructor(
21
+ executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason: E) => void) => void,
22
+ ) {
23
+ super(executor);
24
+ }
25
+
26
+ catch<TResult = never>(
27
+ onrejected?: ((reason: E) => TResult | PromiseLike<TResult>) | null | undefined,
28
+ ): TypedPromise<T | TResult, E> {
29
+ return super.catch(onrejected);
30
+ }
31
+
32
+ static resolve: {
33
+ (): TypedPromise<void, never>;
34
+ <V>(value: V): TypedPromise<Awaited<V>, never>;
35
+ } = <V>(value?: V): TypedPromise<Awaited<V>, never> => {
36
+ return super.resolve(value) as TypedPromise<Awaited<V>, never>;
37
+ };
38
+
39
+ static reject<E extends Error>(reason: E): TypedPromise<never, E> {
40
+ return super.reject(reason);
41
+ }
42
+
43
+ static all<T extends readonly unknown[] | []>(
44
+ values: T,
45
+ ): TypedPromise<{ -readonly [P in keyof T]: Awaited<T[P]> }, InferErrors<T[number]>> {
46
+ return super.all(values) as any;
47
+ }
48
+
49
+ static allSettled<T extends readonly unknown[] | []>(
50
+ values: T,
51
+ ): TypedPromise<{ -readonly [P in keyof T]: SettledResult<T[P]> }, never> {
52
+ return super.allSettled(values) as any;
53
+ }
54
+
55
+ static race<T extends readonly (TypedPromise<any, any> | any)[]>(
56
+ values: T,
57
+ ): TypedPromise<
58
+ T[number] extends TypedPromise<infer U, any>
59
+ ? U
60
+ : T[number] extends PromiseLike<infer U>
61
+ ? U
62
+ : Awaited<T[number]>,
63
+ InferErrors<T[number]>
64
+ > {
65
+ return super.race(values);
66
+ }
67
+ }
package/src/utils.ts CHANGED
@@ -789,11 +789,11 @@ export type DelayOptions = {
789
789
  */
790
790
  export function delay(ms: number, options: DelayOptions = {}): Promise<void> {
791
791
  const { signal } = options;
792
- if (signal?.aborted) return Promise.reject(signal.reason);
792
+ if (signal?.aborted) return Promise.reject(signal.reason ?? new Error('delay aborted'));
793
793
  return new Promise((resolve, reject) => {
794
794
  const abort = () => {
795
795
  clearTimeout(i);
796
- reject(signal?.reason);
796
+ reject(signal?.reason ?? new Error('delay aborted'));
797
797
  };
798
798
  const done = () => {
799
799
  signal?.removeEventListener('abort', abort);