@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.
- package/dist/inference/llm.cjs +35 -13
- package/dist/inference/llm.cjs.map +1 -1
- package/dist/inference/llm.d.cts +10 -5
- package/dist/inference/llm.d.ts +10 -5
- package/dist/inference/llm.d.ts.map +1 -1
- package/dist/inference/llm.js +35 -13
- package/dist/inference/llm.js.map +1 -1
- package/dist/llm/chat_context.d.cts +1 -1
- package/dist/llm/chat_context.d.ts +1 -1
- package/dist/llm/llm.cjs.map +1 -1
- package/dist/llm/llm.d.cts +1 -1
- package/dist/llm/llm.d.ts +1 -1
- package/dist/llm/llm.d.ts.map +1 -1
- package/dist/llm/llm.js.map +1 -1
- package/dist/llm/provider_format/google.cjs.map +1 -1
- package/dist/llm/provider_format/google.d.cts +1 -1
- package/dist/llm/provider_format/google.d.ts +1 -1
- package/dist/llm/provider_format/google.d.ts.map +1 -1
- package/dist/llm/provider_format/google.js.map +1 -1
- package/dist/llm/provider_format/index.d.cts +1 -1
- package/dist/llm/provider_format/index.d.ts +1 -1
- package/dist/llm/provider_format/index.d.ts.map +1 -1
- package/dist/llm/realtime.cjs.map +1 -1
- package/dist/llm/realtime.d.cts +4 -0
- package/dist/llm/realtime.d.ts +4 -0
- package/dist/llm/realtime.d.ts.map +1 -1
- package/dist/llm/realtime.js.map +1 -1
- package/dist/llm/utils.cjs +2 -2
- package/dist/llm/utils.cjs.map +1 -1
- package/dist/llm/utils.d.cts +1 -1
- package/dist/llm/utils.d.ts +1 -1
- package/dist/llm/utils.d.ts.map +1 -1
- package/dist/llm/utils.js +2 -2
- package/dist/llm/utils.js.map +1 -1
- package/dist/llm/zod-utils.cjs +6 -3
- package/dist/llm/zod-utils.cjs.map +1 -1
- package/dist/llm/zod-utils.d.cts +1 -1
- package/dist/llm/zod-utils.d.ts +1 -1
- package/dist/llm/zod-utils.d.ts.map +1 -1
- package/dist/llm/zod-utils.js +6 -3
- package/dist/llm/zod-utils.js.map +1 -1
- package/dist/llm/zod-utils.test.cjs +83 -0
- package/dist/llm/zod-utils.test.cjs.map +1 -1
- package/dist/llm/zod-utils.test.js +83 -0
- package/dist/llm/zod-utils.test.js.map +1 -1
- package/dist/stt/stt.cjs +0 -1
- package/dist/stt/stt.cjs.map +1 -1
- package/dist/stt/stt.d.ts.map +1 -1
- package/dist/stt/stt.js +0 -1
- package/dist/stt/stt.js.map +1 -1
- package/dist/tts/tts.cjs +2 -4
- package/dist/tts/tts.cjs.map +1 -1
- package/dist/tts/tts.d.ts.map +1 -1
- package/dist/tts/tts.js +3 -5
- package/dist/tts/tts.js.map +1 -1
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +7 -0
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/voice/agent_activity.cjs +69 -20
- package/dist/voice/agent_activity.cjs.map +1 -1
- package/dist/voice/agent_activity.d.ts.map +1 -1
- package/dist/voice/agent_activity.js +69 -20
- package/dist/voice/agent_activity.js.map +1 -1
- package/dist/voice/agent_session.cjs +40 -1
- package/dist/voice/agent_session.cjs.map +1 -1
- package/dist/voice/agent_session.d.cts +5 -0
- package/dist/voice/agent_session.d.ts +5 -0
- package/dist/voice/agent_session.d.ts.map +1 -1
- package/dist/voice/agent_session.js +40 -1
- package/dist/voice/agent_session.js.map +1 -1
- package/dist/voice/interruption_detection.test.cjs +114 -0
- package/dist/voice/interruption_detection.test.cjs.map +1 -0
- package/dist/voice/interruption_detection.test.js +113 -0
- package/dist/voice/interruption_detection.test.js.map +1 -0
- package/dist/voice/room_io/room_io.cjs +3 -0
- package/dist/voice/room_io/room_io.cjs.map +1 -1
- package/dist/voice/room_io/room_io.d.cts +1 -0
- package/dist/voice/room_io/room_io.d.ts +1 -0
- package/dist/voice/room_io/room_io.d.ts.map +1 -1
- package/dist/voice/room_io/room_io.js +3 -0
- package/dist/voice/room_io/room_io.js.map +1 -1
- package/package.json +3 -3
- package/src/inference/llm.ts +53 -21
- package/src/llm/__snapshots__/zod-utils.test.ts.snap +218 -0
- package/src/llm/llm.ts +1 -1
- package/src/llm/provider_format/google.ts +4 -4
- package/src/llm/realtime.ts +8 -1
- package/src/llm/utils.ts +7 -2
- package/src/llm/zod-utils.test.ts +101 -0
- package/src/llm/zod-utils.ts +12 -3
- package/src/stt/stt.ts +2 -1
- package/src/tts/tts.ts +7 -5
- package/src/utils.ts +17 -0
- package/src/voice/agent_activity.ts +96 -24
- package/src/voice/agent_session.ts +54 -0
- package/src/voice/interruption_detection.test.ts +151 -0
- 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,
|
|
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,
|
|
16
|
-
const turns: Record<string,
|
|
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,
|
|
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,
|
|
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);
|
package/src/llm/realtime.ts
CHANGED
|
@@ -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: {
|
|
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(
|
|
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', () => {
|
package/src/llm/zod-utils.ts
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|