@livekit/agents 1.0.16 → 1.0.18

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 (99) hide show
  1. package/dist/inference/llm.cjs +35 -13
  2. package/dist/inference/llm.cjs.map +1 -1
  3. package/dist/inference/llm.d.cts +10 -5
  4. package/dist/inference/llm.d.ts +10 -5
  5. package/dist/inference/llm.d.ts.map +1 -1
  6. package/dist/inference/llm.js +35 -13
  7. package/dist/inference/llm.js.map +1 -1
  8. package/dist/llm/chat_context.d.cts +1 -1
  9. package/dist/llm/chat_context.d.ts +1 -1
  10. package/dist/llm/llm.cjs.map +1 -1
  11. package/dist/llm/llm.d.cts +1 -1
  12. package/dist/llm/llm.d.ts +1 -1
  13. package/dist/llm/llm.d.ts.map +1 -1
  14. package/dist/llm/llm.js.map +1 -1
  15. package/dist/llm/provider_format/google.cjs.map +1 -1
  16. package/dist/llm/provider_format/google.d.cts +1 -1
  17. package/dist/llm/provider_format/google.d.ts +1 -1
  18. package/dist/llm/provider_format/google.d.ts.map +1 -1
  19. package/dist/llm/provider_format/google.js.map +1 -1
  20. package/dist/llm/provider_format/index.d.cts +1 -1
  21. package/dist/llm/provider_format/index.d.ts +1 -1
  22. package/dist/llm/provider_format/index.d.ts.map +1 -1
  23. package/dist/llm/realtime.cjs.map +1 -1
  24. package/dist/llm/realtime.d.cts +4 -0
  25. package/dist/llm/realtime.d.ts +4 -0
  26. package/dist/llm/realtime.d.ts.map +1 -1
  27. package/dist/llm/realtime.js.map +1 -1
  28. package/dist/llm/utils.cjs +2 -2
  29. package/dist/llm/utils.cjs.map +1 -1
  30. package/dist/llm/utils.d.cts +1 -1
  31. package/dist/llm/utils.d.ts +1 -1
  32. package/dist/llm/utils.d.ts.map +1 -1
  33. package/dist/llm/utils.js +2 -2
  34. package/dist/llm/utils.js.map +1 -1
  35. package/dist/llm/zod-utils.cjs +6 -3
  36. package/dist/llm/zod-utils.cjs.map +1 -1
  37. package/dist/llm/zod-utils.d.cts +1 -1
  38. package/dist/llm/zod-utils.d.ts +1 -1
  39. package/dist/llm/zod-utils.d.ts.map +1 -1
  40. package/dist/llm/zod-utils.js +6 -3
  41. package/dist/llm/zod-utils.js.map +1 -1
  42. package/dist/llm/zod-utils.test.cjs +83 -0
  43. package/dist/llm/zod-utils.test.cjs.map +1 -1
  44. package/dist/llm/zod-utils.test.js +83 -0
  45. package/dist/llm/zod-utils.test.js.map +1 -1
  46. package/dist/stt/stt.cjs +0 -1
  47. package/dist/stt/stt.cjs.map +1 -1
  48. package/dist/stt/stt.d.ts.map +1 -1
  49. package/dist/stt/stt.js +0 -1
  50. package/dist/stt/stt.js.map +1 -1
  51. package/dist/tts/tts.cjs +2 -4
  52. package/dist/tts/tts.cjs.map +1 -1
  53. package/dist/tts/tts.d.ts.map +1 -1
  54. package/dist/tts/tts.js +3 -5
  55. package/dist/tts/tts.js.map +1 -1
  56. package/dist/utils.cjs.map +1 -1
  57. package/dist/utils.d.cts +7 -0
  58. package/dist/utils.d.ts +7 -0
  59. package/dist/utils.d.ts.map +1 -1
  60. package/dist/utils.js.map +1 -1
  61. package/dist/voice/agent_activity.cjs +69 -20
  62. package/dist/voice/agent_activity.cjs.map +1 -1
  63. package/dist/voice/agent_activity.d.ts.map +1 -1
  64. package/dist/voice/agent_activity.js +69 -20
  65. package/dist/voice/agent_activity.js.map +1 -1
  66. package/dist/voice/agent_session.cjs +40 -1
  67. package/dist/voice/agent_session.cjs.map +1 -1
  68. package/dist/voice/agent_session.d.cts +5 -0
  69. package/dist/voice/agent_session.d.ts +5 -0
  70. package/dist/voice/agent_session.d.ts.map +1 -1
  71. package/dist/voice/agent_session.js +40 -1
  72. package/dist/voice/agent_session.js.map +1 -1
  73. package/dist/voice/interruption_detection.test.cjs +114 -0
  74. package/dist/voice/interruption_detection.test.cjs.map +1 -0
  75. package/dist/voice/interruption_detection.test.js +113 -0
  76. package/dist/voice/interruption_detection.test.js.map +1 -0
  77. package/dist/voice/room_io/room_io.cjs +3 -0
  78. package/dist/voice/room_io/room_io.cjs.map +1 -1
  79. package/dist/voice/room_io/room_io.d.cts +1 -0
  80. package/dist/voice/room_io/room_io.d.ts +1 -0
  81. package/dist/voice/room_io/room_io.d.ts.map +1 -1
  82. package/dist/voice/room_io/room_io.js +3 -0
  83. package/dist/voice/room_io/room_io.js.map +1 -1
  84. package/package.json +3 -3
  85. package/src/inference/llm.ts +53 -21
  86. package/src/llm/__snapshots__/zod-utils.test.ts.snap +218 -0
  87. package/src/llm/llm.ts +1 -1
  88. package/src/llm/provider_format/google.ts +4 -4
  89. package/src/llm/realtime.ts +8 -1
  90. package/src/llm/utils.ts +7 -2
  91. package/src/llm/zod-utils.test.ts +101 -0
  92. package/src/llm/zod-utils.ts +12 -3
  93. package/src/stt/stt.ts +2 -1
  94. package/src/tts/tts.ts +7 -5
  95. package/src/utils.ts +17 -0
  96. package/src/voice/agent_activity.ts +96 -24
  97. package/src/voice/agent_session.ts +54 -0
  98. package/src/voice/interruption_detection.test.ts +151 -0
  99. package/src/voice/room_io/room_io.ts +4 -0
@@ -339,3 +339,221 @@ exports[`Zod Utils > zodSchemaToJsonSchema > Zod v4 schemas > should handle v4 s
339
339
  "type": "object",
340
340
  }
341
341
  `;
342
+
343
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle arrays in strict mode 1`] = `
344
+ {
345
+ "$schema": "http://json-schema.org/draft-07/schema#",
346
+ "additionalProperties": false,
347
+ "properties": {
348
+ "numbers": {
349
+ "items": {
350
+ "type": "number",
351
+ },
352
+ "type": "array",
353
+ },
354
+ "tags": {
355
+ "items": {
356
+ "type": "string",
357
+ },
358
+ "type": "array",
359
+ },
360
+ },
361
+ "required": [
362
+ "tags",
363
+ "numbers",
364
+ ],
365
+ "type": "object",
366
+ }
367
+ `;
368
+
369
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle default values in strict mode 1`] = `
370
+ {
371
+ "$schema": "http://json-schema.org/draft-07/schema#",
372
+ "additionalProperties": false,
373
+ "properties": {
374
+ "active": {
375
+ "default": true,
376
+ "type": "boolean",
377
+ },
378
+ "name": {
379
+ "type": "string",
380
+ },
381
+ "role": {
382
+ "default": "user",
383
+ "type": "string",
384
+ },
385
+ },
386
+ "required": [
387
+ "name",
388
+ "role",
389
+ "active",
390
+ ],
391
+ "type": "object",
392
+ }
393
+ `;
394
+
395
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle nested objects in strict mode 1`] = `
396
+ {
397
+ "$schema": "http://json-schema.org/draft-07/schema#",
398
+ "additionalProperties": false,
399
+ "properties": {
400
+ "metadata": {
401
+ "additionalProperties": false,
402
+ "properties": {
403
+ "created": {
404
+ "type": "string",
405
+ },
406
+ },
407
+ "required": [
408
+ "created",
409
+ ],
410
+ "type": "object",
411
+ },
412
+ "user": {
413
+ "additionalProperties": false,
414
+ "properties": {
415
+ "email": {
416
+ "anyOf": [
417
+ {
418
+ "type": "string",
419
+ },
420
+ {
421
+ "type": "null",
422
+ },
423
+ ],
424
+ },
425
+ "name": {
426
+ "type": "string",
427
+ },
428
+ },
429
+ "required": [
430
+ "name",
431
+ "email",
432
+ ],
433
+ "type": "object",
434
+ },
435
+ },
436
+ "required": [
437
+ "user",
438
+ "metadata",
439
+ ],
440
+ "type": "object",
441
+ }
442
+ `;
443
+
444
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle nullable fields in strict mode 1`] = `
445
+ {
446
+ "$schema": "http://json-schema.org/draft-07/schema#",
447
+ "additionalProperties": false,
448
+ "properties": {
449
+ "optional": {
450
+ "anyOf": [
451
+ {
452
+ "type": "string",
453
+ },
454
+ {
455
+ "type": "null",
456
+ },
457
+ ],
458
+ },
459
+ "required": {
460
+ "type": "string",
461
+ },
462
+ },
463
+ "required": [
464
+ "required",
465
+ "optional",
466
+ ],
467
+ "type": "object",
468
+ }
469
+ `;
470
+
471
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle optional fields in strict mode 1`] = `
472
+ {
473
+ "$schema": "http://json-schema.org/draft-07/schema#",
474
+ "additionalProperties": false,
475
+ "properties": {
476
+ "optional": {
477
+ "anyOf": [
478
+ {
479
+ "type": "string",
480
+ },
481
+ {
482
+ "type": "null",
483
+ },
484
+ ],
485
+ },
486
+ "required": {
487
+ "type": "string",
488
+ },
489
+ },
490
+ "required": [
491
+ "required",
492
+ "optional",
493
+ ],
494
+ "type": "object",
495
+ }
496
+ `;
497
+
498
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should handle v3 schemas in strict mode 1`] = `
499
+ {
500
+ "$schema": "https://json-schema.org/draft/2019-09/schema#",
501
+ "additionalProperties": false,
502
+ "properties": {
503
+ "age": {
504
+ "type": [
505
+ "number",
506
+ "null",
507
+ ],
508
+ },
509
+ "name": {
510
+ "type": "string",
511
+ },
512
+ },
513
+ "required": [
514
+ "name",
515
+ "age",
516
+ ],
517
+ "type": "object",
518
+ }
519
+ `;
520
+
521
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should produce standard JSON schema with strict: false 1`] = `
522
+ {
523
+ "$schema": "http://json-schema.org/draft-07/schema#",
524
+ "additionalProperties": false,
525
+ "properties": {
526
+ "age": {
527
+ "type": "number",
528
+ },
529
+ "name": {
530
+ "type": "string",
531
+ },
532
+ },
533
+ "required": [
534
+ "name",
535
+ "age",
536
+ ],
537
+ "type": "object",
538
+ }
539
+ `;
540
+
541
+ exports[`Zod Utils > zodSchemaToJsonSchema > strict parameter > should produce strict JSON schema with strict: true 1`] = `
542
+ {
543
+ "$schema": "http://json-schema.org/draft-07/schema#",
544
+ "additionalProperties": false,
545
+ "properties": {
546
+ "age": {
547
+ "type": "number",
548
+ },
549
+ "name": {
550
+ "type": "string",
551
+ },
552
+ },
553
+ "required": [
554
+ "name",
555
+ "age",
556
+ ],
557
+ "type": "object",
558
+ }
559
+ `;
package/src/llm/llm.ts CHANGED
@@ -78,7 +78,7 @@ export abstract class LLM extends (EventEmitter as new () => TypedEmitter<LLMCal
78
78
  connOptions?: APIConnectOptions;
79
79
  parallelToolCalls?: boolean;
80
80
  toolChoice?: ToolChoice;
81
- extraKwargs?: Record<string, any>;
81
+ extraKwargs?: Record<string, unknown>;
82
82
  }): LLMStream;
83
83
 
84
84
  /**
@@ -12,11 +12,11 @@ export interface GoogleFormatData {
12
12
  export async function toChatCtx(
13
13
  chatCtx: ChatContext,
14
14
  injectDummyUserMessage: boolean = true,
15
- ): Promise<[Record<string, any>[], GoogleFormatData]> {
16
- const turns: Record<string, any>[] = [];
15
+ ): Promise<[Record<string, unknown>[], GoogleFormatData]> {
16
+ const turns: Record<string, unknown>[] = [];
17
17
  const systemMessages: string[] = [];
18
18
  let currentRole: string | null = null;
19
- let parts: Record<string, any>[] = [];
19
+ let parts: Record<string, unknown>[] = [];
20
20
 
21
21
  // Flatten all grouped tool calls to get individual messages
22
22
  const itemGroups = groupToolCalls(chatCtx);
@@ -104,7 +104,7 @@ export async function toChatCtx(
104
104
  ];
105
105
  }
106
106
 
107
- async function toImagePart(image: ImageContent): Promise<Record<string, any>> {
107
+ async function toImagePart(image: ImageContent): Promise<Record<string, unknown>> {
108
108
  const cacheKey = 'serialized_image';
109
109
  if (!image._cache[cacheKey]) {
110
110
  image._cache[cacheKey] = await serializeImage(image);
@@ -19,6 +19,7 @@ export interface MessageGeneration {
19
19
  messageId: string;
20
20
  textStream: ReadableStream<string>;
21
21
  audioStream: ReadableStream<AudioFrame>;
22
+ modalities?: Promise<('text' | 'audio')[]>;
22
23
  }
23
24
 
24
25
  export interface GenerationCreatedEvent {
@@ -40,6 +41,7 @@ export interface RealtimeCapabilities {
40
41
  turnDetection: boolean;
41
42
  userTranscription: boolean;
42
43
  autoToolReplyGeneration: boolean;
44
+ audioOutput: boolean;
43
45
  }
44
46
 
45
47
  export interface InputTranscriptionCompleted {
@@ -121,7 +123,12 @@ export abstract class RealtimeSession extends EventEmitter {
121
123
  /**
122
124
  * Truncate the message at the given audio end time
123
125
  */
124
- abstract truncate(options: { messageId: string; audioEndMs: number }): Promise<void>;
126
+ abstract truncate(options: {
127
+ messageId: string;
128
+ audioEndMs: number;
129
+ modalities?: ('text' | 'audio')[];
130
+ audioTranscript?: string;
131
+ }): Promise<void>;
125
132
 
126
133
  async close(): Promise<void> {
127
134
  this._mainTask.cancel();
package/src/llm/utils.ts CHANGED
@@ -323,9 +323,14 @@ export function computeChatCtxDiff(oldCtx: ChatContext, newCtx: ChatContext): Di
323
323
  };
324
324
  }
325
325
 
326
- export function toJsonSchema(schema: ToolInputSchema<any>, isOpenai: boolean = true): JSONSchema7 {
326
+ export function toJsonSchema(
327
+ schema: ToolInputSchema<any>,
328
+ isOpenai: boolean = true,
329
+ strict: boolean = false,
330
+ ): JSONSchema7 {
327
331
  if (isZodSchema(schema)) {
328
- return zodSchemaToJsonSchema(schema, isOpenai);
332
+ return zodSchemaToJsonSchema(schema, isOpenai, strict);
329
333
  }
334
+
330
335
  return schema as JSONSchema7;
331
336
  }
@@ -260,6 +260,107 @@ describe('Zod Utils', () => {
260
260
  expect(jsonSchema7).toHaveProperty('properties');
261
261
  });
262
262
  });
263
+
264
+ describe('strict parameter', () => {
265
+ it('should produce strict JSON schema with strict: true', () => {
266
+ const schema = z4.object({
267
+ name: z4.string(),
268
+ age: z4.number(),
269
+ });
270
+
271
+ const strictSchema = zodSchemaToJsonSchema(schema, true, true);
272
+ expect(strictSchema).toMatchSnapshot();
273
+ });
274
+
275
+ it('should handle nullable fields in strict mode', () => {
276
+ const schema = z4.object({
277
+ required: z4.string(),
278
+ optional: z4.string().nullable(),
279
+ });
280
+
281
+ const strictSchema = zodSchemaToJsonSchema(schema, true, true);
282
+ expect(strictSchema).toMatchSnapshot();
283
+ });
284
+
285
+ it('should handle default values in strict mode', () => {
286
+ const schema = z4.object({
287
+ name: z4.string(),
288
+ role: z4.string().default('user'),
289
+ active: z4.boolean().default(true),
290
+ });
291
+
292
+ const strictSchema = zodSchemaToJsonSchema(schema, true, true);
293
+ expect(strictSchema).toMatchSnapshot();
294
+ });
295
+
296
+ it('should handle nested objects in strict mode', () => {
297
+ const schema = z4.object({
298
+ user: z4.object({
299
+ name: z4.string(),
300
+ email: z4.string().nullable(),
301
+ }),
302
+ metadata: z4.object({
303
+ created: z4.string(),
304
+ }),
305
+ });
306
+
307
+ const strictSchema = zodSchemaToJsonSchema(schema, true, true);
308
+ expect(strictSchema).toMatchSnapshot();
309
+ });
310
+
311
+ it('should handle arrays in strict mode', () => {
312
+ const schema = z4.object({
313
+ tags: z4.array(z4.string()),
314
+ numbers: z4.array(z4.number()),
315
+ });
316
+
317
+ const strictSchema = zodSchemaToJsonSchema(schema, true, true);
318
+ expect(strictSchema).toMatchSnapshot();
319
+ });
320
+
321
+ it('should handle v3 schemas in strict mode', () => {
322
+ const schema = z3.object({
323
+ name: z3.string(),
324
+ age: z3.number().optional(),
325
+ });
326
+
327
+ const strictSchema = zodSchemaToJsonSchema(schema, true, true);
328
+ expect(strictSchema).toMatchSnapshot();
329
+ });
330
+
331
+ it('should throw error when using .optional() without .nullable() in strict mode', () => {
332
+ const schema = z4.object({
333
+ required: z4.string(),
334
+ optional: z4.string().optional(),
335
+ });
336
+
337
+ expect(() => zodSchemaToJsonSchema(schema, true, true)).toThrow(
338
+ /uses `.optional\(\)` without `.nullable\(\)` which is not supported by the API/,
339
+ );
340
+ });
341
+
342
+ it('should throw error for nested .optional() fields in strict mode', () => {
343
+ const schema = z4.object({
344
+ user: z4.object({
345
+ name: z4.string(),
346
+ email: z4.string().optional(),
347
+ }),
348
+ });
349
+
350
+ expect(() => zodSchemaToJsonSchema(schema, true, true)).toThrow(
351
+ /uses `.optional\(\)` without `.nullable\(\)` which is not supported by the API/,
352
+ );
353
+ });
354
+
355
+ it('should NOT throw error when using .optional() in non-strict mode', () => {
356
+ const schema = z4.object({
357
+ required: z4.string(),
358
+ optional: z4.string().optional(),
359
+ });
360
+
361
+ expect(() => zodSchemaToJsonSchema(schema, true, false)).not.toThrow();
362
+ });
363
+ });
263
364
  });
264
365
 
265
366
  describe('parseZodSchema', () => {
@@ -2,6 +2,7 @@
2
2
  //
3
3
  // SPDX-License-Identifier: Apache-2.0
4
4
  import type { JSONSchema7 } from 'json-schema';
5
+ import { toStrictJsonSchema } from 'openai/lib/transform';
5
6
  import { zodToJsonSchema as zodToJsonSchemaV3 } from 'zod-to-json-schema';
6
7
  import type * as z3 from 'zod/v3';
7
8
  import * as z4 from 'zod/v4';
@@ -101,12 +102,18 @@ export function isZodObjectSchema(schema: ZodSchema): boolean {
101
102
  * @param isOpenai - Whether to use OpenAI-specific formatting (default: true)
102
103
  * @returns A JSON Schema representation of the Zod schema
103
104
  */
104
- export function zodSchemaToJsonSchema(schema: ZodSchema, isOpenai: boolean = true): JSONSchema7 {
105
+ export function zodSchemaToJsonSchema(
106
+ schema: ZodSchema,
107
+ isOpenai: boolean = true,
108
+ strict: boolean = false,
109
+ ): JSONSchema7 {
110
+ let result: JSONSchema7;
111
+
105
112
  if (isZod4Schema(schema)) {
106
113
  // Zod v4 has native toJSONSchema support
107
114
  // Configuration adapted from Vercel AI SDK to support OpenAPI conversion for Google
108
115
  // Source: https://github.com/vercel/ai/blob/main/packages/provider-utils/src/schema.ts#L255-L258
109
- return z4.toJSONSchema(schema, {
116
+ result = z4.toJSONSchema(schema, {
110
117
  target: 'draft-7',
111
118
  io: 'output',
112
119
  reused: 'inline', // Don't use references by default (to support openapi conversion for google)
@@ -115,11 +122,13 @@ export function zodSchemaToJsonSchema(schema: ZodSchema, isOpenai: boolean = tru
115
122
  // Zod v3 requires the zod-to-json-schema library
116
123
  // Configuration adapted from Vercel AI SDK
117
124
  // $refStrategy: 'none' is equivalent to v4's reused: 'inline'
118
- return zodToJsonSchemaV3(schema, {
125
+ result = zodToJsonSchemaV3(schema, {
119
126
  target: isOpenai ? 'openAi' : 'jsonSchema7',
120
127
  $refStrategy: 'none', // Don't use references by default (to support openapi conversion for google)
121
128
  }) as JSONSchema7;
122
129
  }
130
+
131
+ return strict ? (toStrictJsonSchema(result) as JSONSchema7) : result;
123
132
  }
124
133
 
125
134
  /**
package/src/stt/stt.ts CHANGED
@@ -204,7 +204,8 @@ export abstract class SpeechStream implements AsyncIterableIterator<SpeechEvent>
204
204
  options: { retryable: false },
205
205
  });
206
206
  } else {
207
- this.emitError({ error, recoverable: true });
207
+ // Don't emit error event for recoverable errors during retry loop
208
+ // to avoid ERR_UNHANDLED_ERROR or premature session termination
208
209
  this.logger.warn(
209
210
  { tts: this.#stt.label, attempt: i + 1, error },
210
211
  `failed to recognize speech, retrying in ${retryInterval}s`,
package/src/tts/tts.ts CHANGED
@@ -5,7 +5,7 @@ import type { AudioFrame } from '@livekit/rtc-node';
5
5
  import type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';
6
6
  import { EventEmitter } from 'node:events';
7
7
  import type { ReadableStream } from 'node:stream/web';
8
- import { APIConnectionError, APIStatusError } from '../_exceptions.js';
8
+ import { APIConnectionError, APIError } from '../_exceptions.js';
9
9
  import { log } from '../log.js';
10
10
  import type { TTSMetrics } from '../metrics/base.js';
11
11
  import { DeferredReadableStream } from '../stream/deferred_stream.js';
@@ -161,7 +161,7 @@ export abstract class SynthesizeStream
161
161
  try {
162
162
  return await this.run();
163
163
  } catch (error) {
164
- if (error instanceof APIStatusError) {
164
+ if (error instanceof APIError) {
165
165
  const retryInterval = this._connOptions._intervalForRetry(i);
166
166
 
167
167
  if (this._connOptions.maxRetry === 0 || !error.retryable) {
@@ -174,7 +174,8 @@ export abstract class SynthesizeStream
174
174
  options: { retryable: false },
175
175
  });
176
176
  } else {
177
- this.emitError({ error, recoverable: true });
177
+ // Don't emit error event for recoverable errors during retry loop
178
+ // to avoid ERR_UNHANDLED_ERROR or premature session termination
178
179
  this.logger.warn(
179
180
  { tts: this.#tts.label, attempt: i + 1, error },
180
181
  `failed to synthesize speech, retrying in ${retryInterval}s`,
@@ -388,7 +389,7 @@ export abstract class ChunkedStream implements AsyncIterableIterator<Synthesized
388
389
  try {
389
390
  return await this.run();
390
391
  } catch (error) {
391
- if (error instanceof APIStatusError) {
392
+ if (error instanceof APIError) {
392
393
  const retryInterval = this._connOptions._intervalForRetry(i);
393
394
 
394
395
  if (this._connOptions.maxRetry === 0 || !error.retryable) {
@@ -401,7 +402,8 @@ export abstract class ChunkedStream implements AsyncIterableIterator<Synthesized
401
402
  options: { retryable: false },
402
403
  });
403
404
  } else {
404
- this.emitError({ error, recoverable: true });
405
+ // Don't emit error event for recoverable errors during retry loop
406
+ // to avoid ERR_UNHANDLED_ERROR or premature session termination
405
407
  this.logger.warn(
406
408
  { tts: this.#tts.label, attempt: i + 1, error },
407
409
  `failed to generate TTS completion, retrying in ${retryInterval}s`,
package/src/utils.ts CHANGED
@@ -15,6 +15,23 @@ import { TransformStream, type TransformStreamDefaultController } from 'node:str
15
15
  import { v4 as uuidv4 } from 'uuid';
16
16
  import { log } from './log.js';
17
17
 
18
+ /**
19
+ * Recursively expands all nested properties of a type,
20
+ * resolving aliases so as to inspect the real shape in IDE.
21
+ */
22
+ // eslint-disable-next-line @typescript-eslint/ban-types
23
+ export type Expand<T> = T extends Function
24
+ ? T
25
+ : T extends object
26
+ ? T extends Array<infer U>
27
+ ? Array<Expand<U>>
28
+ : T extends Map<infer K, infer V>
29
+ ? Map<Expand<K>, Expand<V>>
30
+ : T extends Set<infer M>
31
+ ? Set<Expand<M>>
32
+ : { [K in keyof T]: Expand<T[K]> }
33
+ : T;
34
+
18
35
  /** Union of a single and a list of {@link AudioFrame}s */
19
36
  export type AudioBuffer = AudioFrame[] | AudioFrame;
20
37