@livekit/agents 1.0.14 → 1.0.15

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 (50) hide show
  1. package/dist/llm/tool_context.cjs +3 -2
  2. package/dist/llm/tool_context.cjs.map +1 -1
  3. package/dist/llm/tool_context.d.cts +37 -11
  4. package/dist/llm/tool_context.d.ts +37 -11
  5. package/dist/llm/tool_context.d.ts.map +1 -1
  6. package/dist/llm/tool_context.js +4 -3
  7. package/dist/llm/tool_context.js.map +1 -1
  8. package/dist/llm/tool_context.test.cjs +197 -0
  9. package/dist/llm/tool_context.test.cjs.map +1 -1
  10. package/dist/llm/tool_context.test.js +175 -0
  11. package/dist/llm/tool_context.test.js.map +1 -1
  12. package/dist/llm/utils.cjs +17 -11
  13. package/dist/llm/utils.cjs.map +1 -1
  14. package/dist/llm/utils.d.cts +1 -2
  15. package/dist/llm/utils.d.ts +1 -2
  16. package/dist/llm/utils.d.ts.map +1 -1
  17. package/dist/llm/utils.js +17 -11
  18. package/dist/llm/utils.js.map +1 -1
  19. package/dist/llm/zod-utils.cjs +99 -0
  20. package/dist/llm/zod-utils.cjs.map +1 -0
  21. package/dist/llm/zod-utils.d.cts +65 -0
  22. package/dist/llm/zod-utils.d.ts +65 -0
  23. package/dist/llm/zod-utils.d.ts.map +1 -0
  24. package/dist/llm/zod-utils.js +61 -0
  25. package/dist/llm/zod-utils.js.map +1 -0
  26. package/dist/llm/zod-utils.test.cjs +389 -0
  27. package/dist/llm/zod-utils.test.cjs.map +1 -0
  28. package/dist/llm/zod-utils.test.js +372 -0
  29. package/dist/llm/zod-utils.test.js.map +1 -0
  30. package/dist/vad.cjs +16 -0
  31. package/dist/vad.cjs.map +1 -1
  32. package/dist/vad.d.cts +6 -0
  33. package/dist/vad.d.ts +6 -0
  34. package/dist/vad.d.ts.map +1 -1
  35. package/dist/vad.js +16 -0
  36. package/dist/vad.js.map +1 -1
  37. package/dist/voice/generation.cjs +8 -3
  38. package/dist/voice/generation.cjs.map +1 -1
  39. package/dist/voice/generation.d.ts.map +1 -1
  40. package/dist/voice/generation.js +8 -3
  41. package/dist/voice/generation.js.map +1 -1
  42. package/package.json +5 -4
  43. package/src/llm/__snapshots__/zod-utils.test.ts.snap +341 -0
  44. package/src/llm/tool_context.test.ts +210 -1
  45. package/src/llm/tool_context.ts +57 -17
  46. package/src/llm/utils.ts +18 -15
  47. package/src/llm/zod-utils.test.ts +476 -0
  48. package/src/llm/zod-utils.ts +144 -0
  49. package/src/vad.ts +18 -0
  50. package/src/voice/generation.ts +8 -3
@@ -0,0 +1,372 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { z } from "zod";
3
+ import * as z3 from "zod/v3";
4
+ import * as z4 from "zod/v4";
5
+ import {
6
+ isZod4Schema,
7
+ isZodObjectSchema,
8
+ isZodSchema,
9
+ parseZodSchema,
10
+ zodSchemaToJsonSchema
11
+ } from "./zod-utils.js";
12
+ describe("Zod Utils", () => {
13
+ describe("isZod4Schema", () => {
14
+ it("should detect Zod v4 schemas", () => {
15
+ const v4Schema = z4.string();
16
+ expect(isZod4Schema(v4Schema)).toBe(true);
17
+ });
18
+ it("should detect Zod v3 schemas", () => {
19
+ const v3Schema = z3.string();
20
+ expect(isZod4Schema(v3Schema)).toBe(false);
21
+ });
22
+ it("should handle default z import (follows installed version)", () => {
23
+ const schema = z.string();
24
+ expect(typeof isZod4Schema(schema)).toBe("boolean");
25
+ });
26
+ });
27
+ describe("isZodSchema", () => {
28
+ it("should detect Zod v4 schemas", () => {
29
+ const v4Schema = z4.object({ name: z4.string() });
30
+ expect(isZodSchema(v4Schema)).toBe(true);
31
+ });
32
+ it("should detect Zod v3 schemas", () => {
33
+ const v3Schema = z3.object({ name: z3.string() });
34
+ expect(isZodSchema(v3Schema)).toBe(true);
35
+ });
36
+ it("should return false for non-Zod values", () => {
37
+ expect(isZodSchema({})).toBe(false);
38
+ expect(isZodSchema(null)).toBe(false);
39
+ expect(isZodSchema(void 0)).toBe(false);
40
+ expect(isZodSchema("string")).toBe(false);
41
+ expect(isZodSchema(123)).toBe(false);
42
+ expect(isZodSchema({ _def: {} })).toBe(false);
43
+ });
44
+ });
45
+ describe("isZodObjectSchema", () => {
46
+ it("should detect Zod v4 object schemas", () => {
47
+ const objectSchema = z4.object({ name: z4.string() });
48
+ expect(isZodObjectSchema(objectSchema)).toBe(true);
49
+ });
50
+ it("should detect Zod v3 object schemas", () => {
51
+ const objectSchema = z3.object({ name: z3.string() });
52
+ expect(isZodObjectSchema(objectSchema)).toBe(true);
53
+ });
54
+ it("should return false for non-object Zod schemas", () => {
55
+ expect(isZodObjectSchema(z4.string())).toBe(false);
56
+ expect(isZodObjectSchema(z4.number())).toBe(false);
57
+ expect(isZodObjectSchema(z4.array(z4.string()))).toBe(false);
58
+ expect(isZodObjectSchema(z3.string())).toBe(false);
59
+ expect(isZodObjectSchema(z3.number())).toBe(false);
60
+ });
61
+ });
62
+ describe("zodSchemaToJsonSchema", () => {
63
+ describe("Zod v4 schemas", () => {
64
+ it("should convert basic v4 object schema to JSON Schema", () => {
65
+ const schema = z4.object({
66
+ name: z4.string(),
67
+ age: z4.number()
68
+ });
69
+ const jsonSchema = zodSchemaToJsonSchema(schema);
70
+ expect(jsonSchema).toMatchSnapshot();
71
+ });
72
+ it.skip("should handle v4 schemas with descriptions", () => {
73
+ const schema = z4.object({
74
+ location: z4.string().describe("The location to search")
75
+ });
76
+ const jsonSchema = zodSchemaToJsonSchema(schema);
77
+ expect(jsonSchema).toMatchSnapshot();
78
+ });
79
+ it("should handle v4 schemas with optional fields", () => {
80
+ const schema = z4.object({
81
+ required: z4.string(),
82
+ optional: z4.string().optional()
83
+ });
84
+ const jsonSchema = zodSchemaToJsonSchema(schema);
85
+ expect(jsonSchema).toMatchSnapshot();
86
+ });
87
+ it("should handle v4 enum schemas", () => {
88
+ const schema = z4.object({
89
+ color: z4.enum(["red", "blue", "green"])
90
+ });
91
+ const jsonSchema = zodSchemaToJsonSchema(schema);
92
+ expect(jsonSchema).toMatchSnapshot();
93
+ });
94
+ it("should handle v4 array schemas", () => {
95
+ const schema = z4.object({
96
+ tags: z4.array(z4.string())
97
+ });
98
+ const jsonSchema = zodSchemaToJsonSchema(schema);
99
+ expect(jsonSchema).toMatchSnapshot();
100
+ });
101
+ it("should handle v4 nested object schemas", () => {
102
+ const schema = z4.object({
103
+ user: z4.object({
104
+ name: z4.string(),
105
+ email: z4.string()
106
+ })
107
+ });
108
+ const jsonSchema = zodSchemaToJsonSchema(schema);
109
+ expect(jsonSchema).toMatchSnapshot();
110
+ });
111
+ it("should handle v4 schemas with multiple optional fields", () => {
112
+ const schema = z4.object({
113
+ id: z4.string(),
114
+ name: z4.string().optional(),
115
+ age: z4.number().optional(),
116
+ email: z4.string()
117
+ });
118
+ const jsonSchema = zodSchemaToJsonSchema(schema);
119
+ expect(jsonSchema).toMatchSnapshot();
120
+ });
121
+ it("should handle v4 schemas with default values", () => {
122
+ const schema = z4.object({
123
+ name: z4.string(),
124
+ role: z4.string().default("user"),
125
+ active: z4.boolean().default(true)
126
+ });
127
+ const jsonSchema = zodSchemaToJsonSchema(schema);
128
+ expect(jsonSchema).toMatchSnapshot();
129
+ });
130
+ });
131
+ describe("Zod v3 schemas", () => {
132
+ it("should convert basic v3 object schema to JSON Schema", () => {
133
+ const schema = z3.object({
134
+ name: z3.string(),
135
+ age: z3.number()
136
+ });
137
+ const jsonSchema = zodSchemaToJsonSchema(schema);
138
+ expect(jsonSchema).toMatchSnapshot();
139
+ });
140
+ it("should handle v3 schemas with descriptions", () => {
141
+ const schema = z3.object({
142
+ location: z3.string().describe("The location to search")
143
+ });
144
+ const jsonSchema = zodSchemaToJsonSchema(schema);
145
+ expect(jsonSchema).toMatchSnapshot();
146
+ });
147
+ it.skip("should handle v3 schemas with optional fields", () => {
148
+ const schema = z3.object({
149
+ required: z3.string(),
150
+ optional: z3.string().optional()
151
+ });
152
+ const jsonSchema = zodSchemaToJsonSchema(schema);
153
+ expect(jsonSchema).toMatchSnapshot();
154
+ });
155
+ it("should handle v3 enum schemas", () => {
156
+ const schema = z3.object({
157
+ color: z3.enum(["red", "blue", "green"])
158
+ });
159
+ const jsonSchema = zodSchemaToJsonSchema(schema);
160
+ expect(jsonSchema).toMatchSnapshot();
161
+ });
162
+ it("should handle v3 array schemas", () => {
163
+ const schema = z3.object({
164
+ tags: z3.array(z3.string())
165
+ });
166
+ const jsonSchema = zodSchemaToJsonSchema(schema);
167
+ expect(jsonSchema).toMatchSnapshot();
168
+ });
169
+ it("should handle v3 nested object schemas", () => {
170
+ const schema = z3.object({
171
+ user: z3.object({
172
+ name: z3.string(),
173
+ email: z3.string()
174
+ })
175
+ });
176
+ const jsonSchema = zodSchemaToJsonSchema(schema);
177
+ expect(jsonSchema).toMatchSnapshot();
178
+ });
179
+ it("should handle v3 schemas with multiple optional fields", () => {
180
+ const schema = z3.object({
181
+ id: z3.string(),
182
+ name: z3.string().optional(),
183
+ age: z3.number().optional(),
184
+ email: z3.string()
185
+ });
186
+ const jsonSchema = zodSchemaToJsonSchema(schema);
187
+ expect(jsonSchema).toMatchSnapshot();
188
+ });
189
+ it("should handle v3 schemas with default values", () => {
190
+ const schema = z3.object({
191
+ name: z3.string(),
192
+ role: z3.string().default("user"),
193
+ active: z3.boolean().default(true)
194
+ });
195
+ const jsonSchema = zodSchemaToJsonSchema(schema);
196
+ expect(jsonSchema).toMatchSnapshot();
197
+ });
198
+ });
199
+ describe("isOpenai parameter", () => {
200
+ it("should respect isOpenai parameter for v3 schemas", () => {
201
+ const schema = z3.object({ name: z3.string() });
202
+ const openaiSchema = zodSchemaToJsonSchema(schema, true);
203
+ const jsonSchema7 = zodSchemaToJsonSchema(schema, false);
204
+ expect(openaiSchema).toHaveProperty("properties");
205
+ expect(jsonSchema7).toHaveProperty("properties");
206
+ });
207
+ });
208
+ });
209
+ describe("parseZodSchema", () => {
210
+ describe("Zod v4 schemas", () => {
211
+ it("should successfully parse valid v4 data", async () => {
212
+ const schema = z4.object({
213
+ name: z4.string(),
214
+ age: z4.number()
215
+ });
216
+ const result = await parseZodSchema(schema, { name: "John", age: 30 });
217
+ expect(result.success).toBe(true);
218
+ if (result.success) {
219
+ expect(result.data).toEqual({ name: "John", age: 30 });
220
+ }
221
+ });
222
+ it("should fail to parse invalid v4 data", async () => {
223
+ const schema = z4.object({
224
+ name: z4.string(),
225
+ age: z4.number()
226
+ });
227
+ const result = await parseZodSchema(schema, { name: "John", age: "invalid" });
228
+ expect(result.success).toBe(false);
229
+ if (!result.success) {
230
+ expect(result.error).toBeDefined();
231
+ }
232
+ });
233
+ it("should handle v4 optional fields", async () => {
234
+ const schema = z4.object({
235
+ name: z4.string(),
236
+ email: z4.string().optional()
237
+ });
238
+ const result1 = await parseZodSchema(schema, { name: "John" });
239
+ expect(result1.success).toBe(true);
240
+ const result2 = await parseZodSchema(schema, { name: "John", email: "john@example.com" });
241
+ expect(result2.success).toBe(true);
242
+ });
243
+ it("should handle v4 default values", async () => {
244
+ const schema = z4.object({
245
+ name: z4.string(),
246
+ role: z4.string().default("user")
247
+ });
248
+ const result = await parseZodSchema(schema, { name: "John" });
249
+ expect(result.success).toBe(true);
250
+ if (result.success) {
251
+ expect(result.data).toEqual({ name: "John", role: "user" });
252
+ }
253
+ });
254
+ });
255
+ describe("Zod v3 schemas", () => {
256
+ it("should successfully parse valid v3 data", async () => {
257
+ const schema = z3.object({
258
+ name: z3.string(),
259
+ age: z3.number()
260
+ });
261
+ const result = await parseZodSchema(schema, { name: "John", age: 30 });
262
+ expect(result.success).toBe(true);
263
+ if (result.success) {
264
+ expect(result.data).toEqual({ name: "John", age: 30 });
265
+ }
266
+ });
267
+ it("should fail to parse invalid v3 data", async () => {
268
+ const schema = z3.object({
269
+ name: z3.string(),
270
+ age: z3.number()
271
+ });
272
+ const result = await parseZodSchema(schema, { name: "John", age: "invalid" });
273
+ expect(result.success).toBe(false);
274
+ if (!result.success) {
275
+ expect(result.error).toBeDefined();
276
+ }
277
+ });
278
+ it("should handle v3 optional fields", async () => {
279
+ const schema = z3.object({
280
+ name: z3.string(),
281
+ email: z3.string().optional()
282
+ });
283
+ const result1 = await parseZodSchema(schema, { name: "John" });
284
+ expect(result1.success).toBe(true);
285
+ const result2 = await parseZodSchema(schema, { name: "John", email: "john@example.com" });
286
+ expect(result2.success).toBe(true);
287
+ });
288
+ it("should handle v3 default values", async () => {
289
+ const schema = z3.object({
290
+ name: z3.string(),
291
+ role: z3.string().default("user")
292
+ });
293
+ const result = await parseZodSchema(schema, { name: "John" });
294
+ expect(result.success).toBe(true);
295
+ if (result.success) {
296
+ expect(result.data).toEqual({ name: "John", role: "user" });
297
+ }
298
+ });
299
+ });
300
+ });
301
+ describe("Cross-version compatibility", () => {
302
+ it("should handle mixed v3 and v4 schemas in the same codebase", async () => {
303
+ const v3Schema = z3.object({ name: z3.string() });
304
+ const v4Schema = z4.object({ name: z4.string() });
305
+ const v3Result = await parseZodSchema(v3Schema, { name: "John" });
306
+ const v4Result = await parseZodSchema(v4Schema, { name: "Jane" });
307
+ expect(v3Result.success).toBe(true);
308
+ expect(v4Result.success).toBe(true);
309
+ });
310
+ it("should convert both v3 and v4 basic schemas to compatible JSON Schema", () => {
311
+ var _a, _b;
312
+ const v3Schema = z3.object({ count: z3.number() });
313
+ const v4Schema = z4.object({ count: z4.number() });
314
+ const v3Json = zodSchemaToJsonSchema(v3Schema);
315
+ const v4Json = zodSchemaToJsonSchema(v4Schema);
316
+ expect(v3Json.type).toBe("object");
317
+ expect(v4Json.type).toBe("object");
318
+ expect((_a = v3Json.properties.count) == null ? void 0 : _a.type).toBe("number");
319
+ expect((_b = v4Json.properties.count) == null ? void 0 : _b.type).toBe("number");
320
+ });
321
+ it("should handle optional fields consistently across v3 and v4", () => {
322
+ const v3Schema = z3.object({
323
+ required: z3.string(),
324
+ optional: z3.string().optional()
325
+ });
326
+ const v4Schema = z4.object({
327
+ required: z4.string(),
328
+ optional: z4.string().optional()
329
+ });
330
+ const v3Json = zodSchemaToJsonSchema(v3Schema);
331
+ const v4Json = zodSchemaToJsonSchema(v4Schema);
332
+ expect(v3Json.required).toContain("required");
333
+ expect(v4Json.required).toContain("required");
334
+ expect(v4Json.required).not.toContain("optional");
335
+ });
336
+ it("should handle complex schemas with nested objects and arrays consistently", () => {
337
+ const v3Schema = z3.object({
338
+ user: z3.object({
339
+ name: z3.string(),
340
+ email: z3.string().optional()
341
+ }),
342
+ tags: z3.array(z3.string()),
343
+ status: z3.enum(["active", "inactive"])
344
+ });
345
+ const v4Schema = z4.object({
346
+ user: z4.object({
347
+ name: z4.string(),
348
+ email: z4.string().optional()
349
+ }),
350
+ tags: z4.array(z4.string()),
351
+ status: z4.enum(["active", "inactive"])
352
+ });
353
+ const v3Json = zodSchemaToJsonSchema(v3Schema);
354
+ const v4Json = zodSchemaToJsonSchema(v4Schema);
355
+ expect(v3Json.type).toBe(v4Json.type);
356
+ expect(Object.keys(v3Json.properties || {})).toEqual(Object.keys(v4Json.properties || {}));
357
+ const v3User = v3Json.properties.user;
358
+ const v4User = v4Json.properties.user;
359
+ expect(v3User == null ? void 0 : v3User.type).toBe("object");
360
+ expect(v4User == null ? void 0 : v4User.type).toBe("object");
361
+ const v3Tags = v3Json.properties.tags;
362
+ const v4Tags = v4Json.properties.tags;
363
+ expect(v3Tags == null ? void 0 : v3Tags.type).toBe("array");
364
+ expect(v4Tags == null ? void 0 : v4Tags.type).toBe("array");
365
+ const v3Status = v3Json.properties.status;
366
+ const v4Status = v4Json.properties.status;
367
+ expect(v3Status == null ? void 0 : v3Status.enum).toEqual(["active", "inactive"]);
368
+ expect(v4Status == null ? void 0 : v4Status.enum).toEqual(["active", "inactive"]);
369
+ });
370
+ });
371
+ });
372
+ //# sourceMappingURL=zod-utils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/llm/zod-utils.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2025 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { describe, expect, it } from 'vitest';\nimport { z } from 'zod';\nimport * as z3 from 'zod/v3';\nimport * as z4 from 'zod/v4';\nimport {\n isZod4Schema,\n isZodObjectSchema,\n isZodSchema,\n parseZodSchema,\n zodSchemaToJsonSchema,\n} from './zod-utils.js';\n\ntype JSONSchemaProperties = Record<string, Record<string, unknown>>;\n\ndescribe('Zod Utils', () => {\n describe('isZod4Schema', () => {\n it('should detect Zod v4 schemas', () => {\n const v4Schema = z4.string();\n expect(isZod4Schema(v4Schema)).toBe(true);\n });\n\n it('should detect Zod v3 schemas', () => {\n const v3Schema = z3.string();\n expect(isZod4Schema(v3Schema)).toBe(false);\n });\n\n it('should handle default z import (follows installed version)', () => {\n const schema = z.string();\n expect(typeof isZod4Schema(schema)).toBe('boolean');\n });\n });\n\n describe('isZodSchema', () => {\n it('should detect Zod v4 schemas', () => {\n const v4Schema = z4.object({ name: z4.string() });\n expect(isZodSchema(v4Schema)).toBe(true);\n });\n\n it('should detect Zod v3 schemas', () => {\n const v3Schema = z3.object({ name: z3.string() });\n expect(isZodSchema(v3Schema)).toBe(true);\n });\n\n it('should return false for non-Zod values', () => {\n expect(isZodSchema({})).toBe(false);\n expect(isZodSchema(null)).toBe(false);\n expect(isZodSchema(undefined)).toBe(false);\n expect(isZodSchema('string')).toBe(false);\n expect(isZodSchema(123)).toBe(false);\n expect(isZodSchema({ _def: {} })).toBe(false); // missing typeName\n });\n });\n\n describe('isZodObjectSchema', () => {\n it('should detect Zod v4 object schemas', () => {\n const objectSchema = z4.object({ name: z4.string() });\n expect(isZodObjectSchema(objectSchema)).toBe(true);\n });\n\n it('should detect Zod v3 object schemas', () => {\n const objectSchema = z3.object({ name: z3.string() });\n expect(isZodObjectSchema(objectSchema)).toBe(true);\n });\n\n it('should return false for non-object Zod schemas', () => {\n expect(isZodObjectSchema(z4.string())).toBe(false);\n expect(isZodObjectSchema(z4.number())).toBe(false);\n expect(isZodObjectSchema(z4.array(z4.string()))).toBe(false);\n expect(isZodObjectSchema(z3.string())).toBe(false);\n expect(isZodObjectSchema(z3.number())).toBe(false);\n });\n });\n\n describe('zodSchemaToJsonSchema', () => {\n describe('Zod v4 schemas', () => {\n it('should convert basic v4 object schema to JSON Schema', () => {\n const schema = z4.object({\n name: z4.string(),\n age: z4.number(),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it.skip('should handle v4 schemas with descriptions', () => {\n // NOTE: This test is skipped because Zod 3.25.76's v4 alpha doesn't fully support\n // descriptions in toJSONSchema yet. This will work in final Zod v4 release.\n const schema = z4.object({\n location: z4.string().describe('The location to search'),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v4 schemas with optional fields', () => {\n const schema = z4.object({\n required: z4.string(),\n optional: z4.string().optional(),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v4 enum schemas', () => {\n const schema = z4.object({\n color: z4.enum(['red', 'blue', 'green']),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v4 array schemas', () => {\n const schema = z4.object({\n tags: z4.array(z4.string()),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v4 nested object schemas', () => {\n const schema = z4.object({\n user: z4.object({\n name: z4.string(),\n email: z4.string(),\n }),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v4 schemas with multiple optional fields', () => {\n const schema = z4.object({\n id: z4.string(),\n name: z4.string().optional(),\n age: z4.number().optional(),\n email: z4.string(),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v4 schemas with default values', () => {\n const schema = z4.object({\n name: z4.string(),\n role: z4.string().default('user'),\n active: z4.boolean().default(true),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n });\n\n describe('Zod v3 schemas', () => {\n it('should convert basic v3 object schema to JSON Schema', () => {\n const schema = z3.object({\n name: z3.string(),\n age: z3.number(),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v3 schemas with descriptions', () => {\n const schema = z3.object({\n location: z3.string().describe('The location to search'),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it.skip('should handle v3 schemas with optional fields', () => {\n // NOTE: This test is skipped because in Zod 3.25.76, the v3 export's optional()\n // handling in zod-to-json-schema has some quirks. The behavior is correct for\n // the default z import which is what users will typically use.\n const schema = z3.object({\n required: z3.string(),\n optional: z3.string().optional(),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v3 enum schemas', () => {\n const schema = z3.object({\n color: z3.enum(['red', 'blue', 'green']),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v3 array schemas', () => {\n const schema = z3.object({\n tags: z3.array(z3.string()),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v3 nested object schemas', () => {\n const schema = z3.object({\n user: z3.object({\n name: z3.string(),\n email: z3.string(),\n }),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v3 schemas with multiple optional fields', () => {\n const schema = z3.object({\n id: z3.string(),\n name: z3.string().optional(),\n age: z3.number().optional(),\n email: z3.string(),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n\n it('should handle v3 schemas with default values', () => {\n const schema = z3.object({\n name: z3.string(),\n role: z3.string().default('user'),\n active: z3.boolean().default(true),\n });\n\n const jsonSchema = zodSchemaToJsonSchema(schema);\n expect(jsonSchema).toMatchSnapshot();\n });\n });\n\n describe('isOpenai parameter', () => {\n it('should respect isOpenai parameter for v3 schemas', () => {\n const schema = z3.object({ name: z3.string() });\n\n const openaiSchema = zodSchemaToJsonSchema(schema, true);\n const jsonSchema7 = zodSchemaToJsonSchema(schema, false);\n\n // Both should work, just different internal handling\n expect(openaiSchema).toHaveProperty('properties');\n expect(jsonSchema7).toHaveProperty('properties');\n });\n });\n });\n\n describe('parseZodSchema', () => {\n describe('Zod v4 schemas', () => {\n it('should successfully parse valid v4 data', async () => {\n const schema = z4.object({\n name: z4.string(),\n age: z4.number(),\n });\n\n const result = await parseZodSchema(schema, { name: 'John', age: 30 });\n\n expect(result.success).toBe(true);\n if (result.success) {\n expect(result.data).toEqual({ name: 'John', age: 30 });\n }\n });\n\n it('should fail to parse invalid v4 data', async () => {\n const schema = z4.object({\n name: z4.string(),\n age: z4.number(),\n });\n\n const result = await parseZodSchema(schema, { name: 'John', age: 'invalid' });\n\n expect(result.success).toBe(false);\n if (!result.success) {\n expect(result.error).toBeDefined();\n }\n });\n\n it('should handle v4 optional fields', async () => {\n const schema = z4.object({\n name: z4.string(),\n email: z4.string().optional(),\n });\n\n const result1 = await parseZodSchema(schema, { name: 'John' });\n expect(result1.success).toBe(true);\n\n const result2 = await parseZodSchema(schema, { name: 'John', email: 'john@example.com' });\n expect(result2.success).toBe(true);\n });\n\n it('should handle v4 default values', async () => {\n const schema = z4.object({\n name: z4.string(),\n role: z4.string().default('user'),\n });\n\n const result = await parseZodSchema(schema, { name: 'John' });\n\n expect(result.success).toBe(true);\n if (result.success) {\n expect(result.data).toEqual({ name: 'John', role: 'user' });\n }\n });\n });\n\n describe('Zod v3 schemas', () => {\n it('should successfully parse valid v3 data', async () => {\n const schema = z3.object({\n name: z3.string(),\n age: z3.number(),\n });\n\n const result = await parseZodSchema(schema, { name: 'John', age: 30 });\n\n expect(result.success).toBe(true);\n if (result.success) {\n expect(result.data).toEqual({ name: 'John', age: 30 });\n }\n });\n\n it('should fail to parse invalid v3 data', async () => {\n const schema = z3.object({\n name: z3.string(),\n age: z3.number(),\n });\n\n const result = await parseZodSchema(schema, { name: 'John', age: 'invalid' });\n\n expect(result.success).toBe(false);\n if (!result.success) {\n expect(result.error).toBeDefined();\n }\n });\n\n it('should handle v3 optional fields', async () => {\n const schema = z3.object({\n name: z3.string(),\n email: z3.string().optional(),\n });\n\n const result1 = await parseZodSchema(schema, { name: 'John' });\n expect(result1.success).toBe(true);\n\n const result2 = await parseZodSchema(schema, { name: 'John', email: 'john@example.com' });\n expect(result2.success).toBe(true);\n });\n\n it('should handle v3 default values', async () => {\n const schema = z3.object({\n name: z3.string(),\n role: z3.string().default('user'),\n });\n\n const result = await parseZodSchema(schema, { name: 'John' });\n\n expect(result.success).toBe(true);\n if (result.success) {\n expect(result.data).toEqual({ name: 'John', role: 'user' });\n }\n });\n });\n });\n\n describe('Cross-version compatibility', () => {\n it('should handle mixed v3 and v4 schemas in the same codebase', async () => {\n const v3Schema = z3.object({ name: z3.string() });\n const v4Schema = z4.object({ name: z4.string() });\n\n const v3Result = await parseZodSchema(v3Schema, { name: 'John' });\n const v4Result = await parseZodSchema(v4Schema, { name: 'Jane' });\n\n expect(v3Result.success).toBe(true);\n expect(v4Result.success).toBe(true);\n });\n\n it('should convert both v3 and v4 basic schemas to compatible JSON Schema', () => {\n const v3Schema = z3.object({ count: z3.number() });\n const v4Schema = z4.object({ count: z4.number() });\n\n const v3Json = zodSchemaToJsonSchema(v3Schema);\n const v4Json = zodSchemaToJsonSchema(v4Schema);\n\n // Both should produce valid JSON Schema with same structure\n expect(v3Json.type).toBe('object');\n expect(v4Json.type).toBe('object');\n expect((v3Json.properties as JSONSchemaProperties).count?.type).toBe('number');\n expect((v4Json.properties as JSONSchemaProperties).count?.type).toBe('number');\n });\n\n it('should handle optional fields consistently across v3 and v4', () => {\n const v3Schema = z3.object({\n required: z3.string(),\n optional: z3.string().optional(),\n });\n const v4Schema = z4.object({\n required: z4.string(),\n optional: z4.string().optional(),\n });\n\n const v3Json = zodSchemaToJsonSchema(v3Schema);\n const v4Json = zodSchemaToJsonSchema(v4Schema);\n\n // Both should mark 'required' as required\n expect(v3Json.required).toContain('required');\n expect(v4Json.required).toContain('required');\n\n // v4 should NOT mark 'optional' as required\n expect(v4Json.required).not.toContain('optional');\n\n // NOTE: v3's optional handling in zod-to-json-schema (for the v3 export) has quirks\n // in the alpha version 3.25.76. The default z import works correctly for users.\n });\n\n it('should handle complex schemas with nested objects and arrays consistently', () => {\n const v3Schema = z3.object({\n user: z3.object({\n name: z3.string(),\n email: z3.string().optional(),\n }),\n tags: z3.array(z3.string()),\n status: z3.enum(['active', 'inactive']),\n });\n\n const v4Schema = z4.object({\n user: z4.object({\n name: z4.string(),\n email: z4.string().optional(),\n }),\n tags: z4.array(z4.string()),\n status: z4.enum(['active', 'inactive']),\n });\n\n const v3Json = zodSchemaToJsonSchema(v3Schema);\n const v4Json = zodSchemaToJsonSchema(v4Schema);\n\n // Check structure compatibility\n expect(v3Json.type).toBe(v4Json.type);\n expect(Object.keys(v3Json.properties || {})).toEqual(Object.keys(v4Json.properties || {}));\n\n // Check nested object\n const v3User = (v3Json.properties as JSONSchemaProperties).user;\n const v4User = (v4Json.properties as JSONSchemaProperties).user;\n expect(v3User?.type).toBe('object');\n expect(v4User?.type).toBe('object');\n\n // Check array\n const v3Tags = (v3Json.properties as JSONSchemaProperties).tags;\n const v4Tags = (v4Json.properties as JSONSchemaProperties).tags;\n expect(v3Tags?.type).toBe('array');\n expect(v4Tags?.type).toBe('array');\n\n // Check enum\n const v3Status = (v3Json.properties as JSONSchemaProperties).status;\n const v4Status = (v4Json.properties as JSONSchemaProperties).status;\n expect(v3Status?.enum).toEqual(['active', 'inactive']);\n expect(v4Status?.enum).toEqual(['active', 'inactive']);\n });\n });\n});\n"],"mappings":"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","names":[]}
package/dist/vad.cjs CHANGED
@@ -130,6 +130,22 @@ class VADStream {
130
130
  }
131
131
  }
132
132
  }
133
+ /**
134
+ * Safely send a VAD event to the output stream, handling writer release errors during shutdown.
135
+ * @returns true if the event was sent, false if the stream is closing
136
+ * @throws Error if an unexpected error occurs
137
+ */
138
+ sendVADEvent(event) {
139
+ if (this.closed) {
140
+ return false;
141
+ }
142
+ try {
143
+ this.outputWriter.write(event);
144
+ return true;
145
+ } catch (e) {
146
+ throw e;
147
+ }
148
+ }
133
149
  updateInputStream(audioStream) {
134
150
  this.deferredInputStream.setSource(audioStream);
135
151
  }
package/dist/vad.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/vad.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport type {\n ReadableStream,\n ReadableStreamDefaultReader,\n WritableStreamDefaultWriter,\n} from 'node:stream/web';\nimport { log } from './log.js';\nimport type { VADMetrics } from './metrics/base.js';\nimport { DeferredReadableStream } from './stream/deferred_stream.js';\nimport { IdentityTransform } from './stream/identity_transform.js';\n\nexport enum VADEventType {\n START_OF_SPEECH,\n INFERENCE_DONE,\n END_OF_SPEECH,\n METRICS_COLLECTED,\n}\n\nexport interface VADEvent {\n /** Type of the VAD event (e.g., start of speech, end of speech, inference done). */\n type: VADEventType;\n /**\n * Index of the audio sample where the event occurred, relative to the inference sample rate.\n */\n samplesIndex: number;\n /** Timestamp when the event was fired. */\n timestamp: number;\n /** Duration of the speech segment. */\n speechDuration: number;\n /** Duration of the silence segment. */\n silenceDuration: number;\n /**\n * List of audio frames associated with the speech.\n *\n * @remarks\n * - For `start_of_speech` events, this contains the audio chunks that triggered the detection.\n * - For `inference_done` events, this contains the audio chunks that were processed.\n * - For `end_of_speech` events, this contains the complete user speech.\n */\n frames: AudioFrame[];\n /** Probability that speech is present (only for `INFERENCE_DONE` events). */\n probability: number;\n /** Time taken to perform the inference, in seconds (only for `INFERENCE_DONE` events). */\n inferenceDuration: number;\n /** Indicates whether speech was detected in the frames. */\n speaking: boolean;\n /** Threshold used to detect silence. */\n rawAccumulatedSilence: number;\n /** Threshold used to detect speech. */\n rawAccumulatedSpeech: number;\n}\n\nexport interface VADCapabilities {\n updateInterval: number;\n}\n\nexport type VADCallbacks = {\n ['metrics_collected']: (metrics: VADMetrics) => void;\n};\n\nexport abstract class VAD extends (EventEmitter as new () => TypedEmitter<VADCallbacks>) {\n #capabilities: VADCapabilities;\n abstract label: string;\n\n constructor(capabilities: VADCapabilities) {\n super();\n this.#capabilities = capabilities;\n }\n\n get capabilities(): VADCapabilities {\n return this.#capabilities;\n }\n\n /**\n * Returns a {@link VADStream} that can be used to push audio frames and receive VAD events.\n */\n abstract stream(): VADStream;\n}\n\nexport abstract class VADStream implements AsyncIterableIterator<VADEvent> {\n protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n protected input = new IdentityTransform<AudioFrame | typeof VADStream.FLUSH_SENTINEL>();\n protected output = new IdentityTransform<VADEvent>();\n protected inputWriter: WritableStreamDefaultWriter<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected inputReader: ReadableStreamDefaultReader<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected outputWriter: WritableStreamDefaultWriter<VADEvent>;\n protected outputReader: ReadableStreamDefaultReader<VADEvent>;\n protected closed = false;\n protected inputClosed = false;\n\n #vad: VAD;\n #lastActivityTime = BigInt(0);\n private logger = log();\n private deferredInputStream: DeferredReadableStream<AudioFrame>;\n\n private metricsStream: ReadableStream<VADEvent>;\n constructor(vad: VAD) {\n this.#vad = vad;\n this.deferredInputStream = new DeferredReadableStream<AudioFrame>();\n\n this.inputWriter = this.input.writable.getWriter();\n this.inputReader = this.input.readable.getReader();\n this.outputWriter = this.output.writable.getWriter();\n\n const [outputStream, metricsStream] = this.output.readable.tee();\n this.metricsStream = metricsStream;\n this.outputReader = outputStream.getReader();\n\n this.pumpDeferredStream();\n this.monitorMetrics();\n }\n\n /**\n * Reads from the deferred input stream and forwards chunks to the input writer.\n *\n * Note: we can't just do this.deferredInputStream.stream.pipeTo(this.input.writable)\n * because the inputWriter locks the this.input.writable stream. All writes must go through\n * the inputWriter.\n */\n private async pumpDeferredStream() {\n const reader = this.deferredInputStream.stream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n await this.inputWriter.write(value);\n }\n } catch (e) {\n this.logger.error(`Error pumping deferred stream: ${e}`);\n throw e;\n } finally {\n reader.releaseLock();\n }\n }\n\n protected async monitorMetrics() {\n let inferenceDurationTotalMs = 0;\n let inferenceCount = 0;\n const metricsReader = this.metricsStream.getReader();\n while (true) {\n const { done, value } = await metricsReader.read();\n if (done) {\n break;\n }\n switch (value.type) {\n case VADEventType.START_OF_SPEECH:\n inferenceCount++;\n if (inferenceCount >= 1 / this.#vad.capabilities.updateInterval) {\n this.#vad.emit('metrics_collected', {\n type: 'vad_metrics',\n timestamp: Date.now(),\n idleTimeMs: Math.trunc(\n Number((process.hrtime.bigint() - this.#lastActivityTime) / BigInt(1000000)),\n ),\n inferenceDurationTotalMs,\n inferenceCount,\n label: this.#vad.label,\n });\n\n inferenceCount = 0;\n inferenceDurationTotalMs = 0;\n }\n break;\n case VADEventType.INFERENCE_DONE:\n inferenceDurationTotalMs += Math.round(value.inferenceDuration);\n this.#lastActivityTime = process.hrtime.bigint();\n break;\n case VADEventType.END_OF_SPEECH:\n this.#lastActivityTime = process.hrtime.bigint();\n break;\n }\n }\n }\n\n updateInputStream(audioStream: ReadableStream<AudioFrame>) {\n this.deferredInputStream.setSource(audioStream);\n }\n\n detachInputStream() {\n this.deferredInputStream.detachSource();\n }\n\n /** @deprecated Use `updateInputStream` instead */\n pushFrame(frame: AudioFrame) {\n // TODO(AJS-395): remove this method\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(frame);\n }\n\n flush() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(VADStream.FLUSH_SENTINEL);\n }\n\n endInput() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputClosed = true;\n this.input.writable.close();\n }\n\n async next(): Promise<IteratorResult<VADEvent>> {\n return this.outputReader.read().then(({ done, value }) => {\n if (done) {\n return { done: true, value: undefined };\n }\n return { done: false, value };\n });\n }\n\n close() {\n this.outputWriter.releaseLock();\n this.outputReader.cancel();\n this.output.writable.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): VADStream {\n return this;\n }\n}\n"],"mappings":"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","names":["VADEventType"]}
1
+ {"version":3,"sources":["../src/vad.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport type {\n ReadableStream,\n ReadableStreamDefaultReader,\n WritableStreamDefaultWriter,\n} from 'node:stream/web';\nimport { log } from './log.js';\nimport type { VADMetrics } from './metrics/base.js';\nimport { DeferredReadableStream } from './stream/deferred_stream.js';\nimport { IdentityTransform } from './stream/identity_transform.js';\n\nexport enum VADEventType {\n START_OF_SPEECH,\n INFERENCE_DONE,\n END_OF_SPEECH,\n METRICS_COLLECTED,\n}\n\nexport interface VADEvent {\n /** Type of the VAD event (e.g., start of speech, end of speech, inference done). */\n type: VADEventType;\n /**\n * Index of the audio sample where the event occurred, relative to the inference sample rate.\n */\n samplesIndex: number;\n /** Timestamp when the event was fired. */\n timestamp: number;\n /** Duration of the speech segment. */\n speechDuration: number;\n /** Duration of the silence segment. */\n silenceDuration: number;\n /**\n * List of audio frames associated with the speech.\n *\n * @remarks\n * - For `start_of_speech` events, this contains the audio chunks that triggered the detection.\n * - For `inference_done` events, this contains the audio chunks that were processed.\n * - For `end_of_speech` events, this contains the complete user speech.\n */\n frames: AudioFrame[];\n /** Probability that speech is present (only for `INFERENCE_DONE` events). */\n probability: number;\n /** Time taken to perform the inference, in seconds (only for `INFERENCE_DONE` events). */\n inferenceDuration: number;\n /** Indicates whether speech was detected in the frames. */\n speaking: boolean;\n /** Threshold used to detect silence. */\n rawAccumulatedSilence: number;\n /** Threshold used to detect speech. */\n rawAccumulatedSpeech: number;\n}\n\nexport interface VADCapabilities {\n updateInterval: number;\n}\n\nexport type VADCallbacks = {\n ['metrics_collected']: (metrics: VADMetrics) => void;\n};\n\nexport abstract class VAD extends (EventEmitter as new () => TypedEmitter<VADCallbacks>) {\n #capabilities: VADCapabilities;\n abstract label: string;\n\n constructor(capabilities: VADCapabilities) {\n super();\n this.#capabilities = capabilities;\n }\n\n get capabilities(): VADCapabilities {\n return this.#capabilities;\n }\n\n /**\n * Returns a {@link VADStream} that can be used to push audio frames and receive VAD events.\n */\n abstract stream(): VADStream;\n}\n\nexport abstract class VADStream implements AsyncIterableIterator<VADEvent> {\n protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n protected input = new IdentityTransform<AudioFrame | typeof VADStream.FLUSH_SENTINEL>();\n protected output = new IdentityTransform<VADEvent>();\n protected inputWriter: WritableStreamDefaultWriter<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected inputReader: ReadableStreamDefaultReader<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected outputWriter: WritableStreamDefaultWriter<VADEvent>;\n protected outputReader: ReadableStreamDefaultReader<VADEvent>;\n protected closed = false;\n protected inputClosed = false;\n\n #vad: VAD;\n #lastActivityTime = BigInt(0);\n private logger = log();\n private deferredInputStream: DeferredReadableStream<AudioFrame>;\n\n private metricsStream: ReadableStream<VADEvent>;\n constructor(vad: VAD) {\n this.#vad = vad;\n this.deferredInputStream = new DeferredReadableStream<AudioFrame>();\n\n this.inputWriter = this.input.writable.getWriter();\n this.inputReader = this.input.readable.getReader();\n this.outputWriter = this.output.writable.getWriter();\n\n const [outputStream, metricsStream] = this.output.readable.tee();\n this.metricsStream = metricsStream;\n this.outputReader = outputStream.getReader();\n\n this.pumpDeferredStream();\n this.monitorMetrics();\n }\n\n /**\n * Reads from the deferred input stream and forwards chunks to the input writer.\n *\n * Note: we can't just do this.deferredInputStream.stream.pipeTo(this.input.writable)\n * because the inputWriter locks the this.input.writable stream. All writes must go through\n * the inputWriter.\n */\n private async pumpDeferredStream() {\n const reader = this.deferredInputStream.stream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n await this.inputWriter.write(value);\n }\n } catch (e) {\n this.logger.error(`Error pumping deferred stream: ${e}`);\n throw e;\n } finally {\n reader.releaseLock();\n }\n }\n\n protected async monitorMetrics() {\n let inferenceDurationTotalMs = 0;\n let inferenceCount = 0;\n const metricsReader = this.metricsStream.getReader();\n while (true) {\n const { done, value } = await metricsReader.read();\n if (done) {\n break;\n }\n switch (value.type) {\n case VADEventType.START_OF_SPEECH:\n inferenceCount++;\n if (inferenceCount >= 1 / this.#vad.capabilities.updateInterval) {\n this.#vad.emit('metrics_collected', {\n type: 'vad_metrics',\n timestamp: Date.now(),\n idleTimeMs: Math.trunc(\n Number((process.hrtime.bigint() - this.#lastActivityTime) / BigInt(1000000)),\n ),\n inferenceDurationTotalMs,\n inferenceCount,\n label: this.#vad.label,\n });\n\n inferenceCount = 0;\n inferenceDurationTotalMs = 0;\n }\n break;\n case VADEventType.INFERENCE_DONE:\n inferenceDurationTotalMs += Math.round(value.inferenceDuration);\n this.#lastActivityTime = process.hrtime.bigint();\n break;\n case VADEventType.END_OF_SPEECH:\n this.#lastActivityTime = process.hrtime.bigint();\n break;\n }\n }\n }\n\n /**\n * Safely send a VAD event to the output stream, handling writer release errors during shutdown.\n * @returns true if the event was sent, false if the stream is closing\n * @throws Error if an unexpected error occurs\n */\n protected sendVADEvent(event: VADEvent): boolean {\n if (this.closed) {\n return false;\n }\n\n try {\n this.outputWriter.write(event);\n return true;\n } catch (e) {\n throw e;\n }\n }\n\n updateInputStream(audioStream: ReadableStream<AudioFrame>) {\n this.deferredInputStream.setSource(audioStream);\n }\n\n detachInputStream() {\n this.deferredInputStream.detachSource();\n }\n\n /** @deprecated Use `updateInputStream` instead */\n pushFrame(frame: AudioFrame) {\n // TODO(AJS-395): remove this method\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(frame);\n }\n\n flush() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(VADStream.FLUSH_SENTINEL);\n }\n\n endInput() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputClosed = true;\n this.input.writable.close();\n }\n\n async next(): Promise<IteratorResult<VADEvent>> {\n return this.outputReader.read().then(({ done, value }) => {\n if (done) {\n return { done: true, value: undefined };\n }\n return { done: false, value };\n });\n }\n\n close() {\n this.outputWriter.releaseLock();\n this.outputReader.cancel();\n this.output.writable.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): VADStream {\n return this;\n }\n}\n"],"mappings":"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","names":["VADEventType"]}
package/dist/vad.d.cts CHANGED
@@ -84,6 +84,12 @@ export declare abstract class VADStream implements AsyncIterableIterator<VADEven
84
84
  */
85
85
  private pumpDeferredStream;
86
86
  protected monitorMetrics(): Promise<void>;
87
+ /**
88
+ * Safely send a VAD event to the output stream, handling writer release errors during shutdown.
89
+ * @returns true if the event was sent, false if the stream is closing
90
+ * @throws Error if an unexpected error occurs
91
+ */
92
+ protected sendVADEvent(event: VADEvent): boolean;
87
93
  updateInputStream(audioStream: ReadableStream<AudioFrame>): void;
88
94
  detachInputStream(): void;
89
95
  /** @deprecated Use `updateInputStream` instead */
package/dist/vad.d.ts CHANGED
@@ -84,6 +84,12 @@ export declare abstract class VADStream implements AsyncIterableIterator<VADEven
84
84
  */
85
85
  private pumpDeferredStream;
86
86
  protected monitorMetrics(): Promise<void>;
87
+ /**
88
+ * Safely send a VAD event to the output stream, handling writer release errors during shutdown.
89
+ * @returns true if the event was sent, false if the stream is closing
90
+ * @throws Error if an unexpected error occurs
91
+ */
92
+ protected sendVADEvent(event: VADEvent): boolean;
87
93
  updateInputStream(audioStream: ReadableStream<AudioFrame>): void;
88
94
  detachInputStream(): void;
89
95
  /** @deprecated Use `updateInputStream` instead */
package/dist/vad.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vad.d.ts","sourceRoot":"","sources":["../src/vad.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"vad.d.ts","sourceRoot":"","sources":["../src/vad.ts"],"names":[],"mappings":"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"}
package/dist/vad.js CHANGED
@@ -105,6 +105,22 @@ class VADStream {
105
105
  }
106
106
  }
107
107
  }
108
+ /**
109
+ * Safely send a VAD event to the output stream, handling writer release errors during shutdown.
110
+ * @returns true if the event was sent, false if the stream is closing
111
+ * @throws Error if an unexpected error occurs
112
+ */
113
+ sendVADEvent(event) {
114
+ if (this.closed) {
115
+ return false;
116
+ }
117
+ try {
118
+ this.outputWriter.write(event);
119
+ return true;
120
+ } catch (e) {
121
+ throw e;
122
+ }
123
+ }
108
124
  updateInputStream(audioStream) {
109
125
  this.deferredInputStream.setSource(audioStream);
110
126
  }
package/dist/vad.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/vad.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport type {\n ReadableStream,\n ReadableStreamDefaultReader,\n WritableStreamDefaultWriter,\n} from 'node:stream/web';\nimport { log } from './log.js';\nimport type { VADMetrics } from './metrics/base.js';\nimport { DeferredReadableStream } from './stream/deferred_stream.js';\nimport { IdentityTransform } from './stream/identity_transform.js';\n\nexport enum VADEventType {\n START_OF_SPEECH,\n INFERENCE_DONE,\n END_OF_SPEECH,\n METRICS_COLLECTED,\n}\n\nexport interface VADEvent {\n /** Type of the VAD event (e.g., start of speech, end of speech, inference done). */\n type: VADEventType;\n /**\n * Index of the audio sample where the event occurred, relative to the inference sample rate.\n */\n samplesIndex: number;\n /** Timestamp when the event was fired. */\n timestamp: number;\n /** Duration of the speech segment. */\n speechDuration: number;\n /** Duration of the silence segment. */\n silenceDuration: number;\n /**\n * List of audio frames associated with the speech.\n *\n * @remarks\n * - For `start_of_speech` events, this contains the audio chunks that triggered the detection.\n * - For `inference_done` events, this contains the audio chunks that were processed.\n * - For `end_of_speech` events, this contains the complete user speech.\n */\n frames: AudioFrame[];\n /** Probability that speech is present (only for `INFERENCE_DONE` events). */\n probability: number;\n /** Time taken to perform the inference, in seconds (only for `INFERENCE_DONE` events). */\n inferenceDuration: number;\n /** Indicates whether speech was detected in the frames. */\n speaking: boolean;\n /** Threshold used to detect silence. */\n rawAccumulatedSilence: number;\n /** Threshold used to detect speech. */\n rawAccumulatedSpeech: number;\n}\n\nexport interface VADCapabilities {\n updateInterval: number;\n}\n\nexport type VADCallbacks = {\n ['metrics_collected']: (metrics: VADMetrics) => void;\n};\n\nexport abstract class VAD extends (EventEmitter as new () => TypedEmitter<VADCallbacks>) {\n #capabilities: VADCapabilities;\n abstract label: string;\n\n constructor(capabilities: VADCapabilities) {\n super();\n this.#capabilities = capabilities;\n }\n\n get capabilities(): VADCapabilities {\n return this.#capabilities;\n }\n\n /**\n * Returns a {@link VADStream} that can be used to push audio frames and receive VAD events.\n */\n abstract stream(): VADStream;\n}\n\nexport abstract class VADStream implements AsyncIterableIterator<VADEvent> {\n protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n protected input = new IdentityTransform<AudioFrame | typeof VADStream.FLUSH_SENTINEL>();\n protected output = new IdentityTransform<VADEvent>();\n protected inputWriter: WritableStreamDefaultWriter<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected inputReader: ReadableStreamDefaultReader<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected outputWriter: WritableStreamDefaultWriter<VADEvent>;\n protected outputReader: ReadableStreamDefaultReader<VADEvent>;\n protected closed = false;\n protected inputClosed = false;\n\n #vad: VAD;\n #lastActivityTime = BigInt(0);\n private logger = log();\n private deferredInputStream: DeferredReadableStream<AudioFrame>;\n\n private metricsStream: ReadableStream<VADEvent>;\n constructor(vad: VAD) {\n this.#vad = vad;\n this.deferredInputStream = new DeferredReadableStream<AudioFrame>();\n\n this.inputWriter = this.input.writable.getWriter();\n this.inputReader = this.input.readable.getReader();\n this.outputWriter = this.output.writable.getWriter();\n\n const [outputStream, metricsStream] = this.output.readable.tee();\n this.metricsStream = metricsStream;\n this.outputReader = outputStream.getReader();\n\n this.pumpDeferredStream();\n this.monitorMetrics();\n }\n\n /**\n * Reads from the deferred input stream and forwards chunks to the input writer.\n *\n * Note: we can't just do this.deferredInputStream.stream.pipeTo(this.input.writable)\n * because the inputWriter locks the this.input.writable stream. All writes must go through\n * the inputWriter.\n */\n private async pumpDeferredStream() {\n const reader = this.deferredInputStream.stream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n await this.inputWriter.write(value);\n }\n } catch (e) {\n this.logger.error(`Error pumping deferred stream: ${e}`);\n throw e;\n } finally {\n reader.releaseLock();\n }\n }\n\n protected async monitorMetrics() {\n let inferenceDurationTotalMs = 0;\n let inferenceCount = 0;\n const metricsReader = this.metricsStream.getReader();\n while (true) {\n const { done, value } = await metricsReader.read();\n if (done) {\n break;\n }\n switch (value.type) {\n case VADEventType.START_OF_SPEECH:\n inferenceCount++;\n if (inferenceCount >= 1 / this.#vad.capabilities.updateInterval) {\n this.#vad.emit('metrics_collected', {\n type: 'vad_metrics',\n timestamp: Date.now(),\n idleTimeMs: Math.trunc(\n Number((process.hrtime.bigint() - this.#lastActivityTime) / BigInt(1000000)),\n ),\n inferenceDurationTotalMs,\n inferenceCount,\n label: this.#vad.label,\n });\n\n inferenceCount = 0;\n inferenceDurationTotalMs = 0;\n }\n break;\n case VADEventType.INFERENCE_DONE:\n inferenceDurationTotalMs += Math.round(value.inferenceDuration);\n this.#lastActivityTime = process.hrtime.bigint();\n break;\n case VADEventType.END_OF_SPEECH:\n this.#lastActivityTime = process.hrtime.bigint();\n break;\n }\n }\n }\n\n updateInputStream(audioStream: ReadableStream<AudioFrame>) {\n this.deferredInputStream.setSource(audioStream);\n }\n\n detachInputStream() {\n this.deferredInputStream.detachSource();\n }\n\n /** @deprecated Use `updateInputStream` instead */\n pushFrame(frame: AudioFrame) {\n // TODO(AJS-395): remove this method\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(frame);\n }\n\n flush() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(VADStream.FLUSH_SENTINEL);\n }\n\n endInput() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputClosed = true;\n this.input.writable.close();\n }\n\n async next(): Promise<IteratorResult<VADEvent>> {\n return this.outputReader.read().then(({ done, value }) => {\n if (done) {\n return { done: true, value: undefined };\n }\n return { done: false, value };\n });\n }\n\n close() {\n this.outputWriter.releaseLock();\n this.outputReader.cancel();\n this.output.writable.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): VADStream {\n return this;\n }\n}\n"],"mappings":"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","names":["VADEventType"]}
1
+ {"version":3,"sources":["../src/vad.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2024 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { TypedEventEmitter as TypedEmitter } from '@livekit/typed-emitter';\nimport { EventEmitter } from 'node:events';\nimport type {\n ReadableStream,\n ReadableStreamDefaultReader,\n WritableStreamDefaultWriter,\n} from 'node:stream/web';\nimport { log } from './log.js';\nimport type { VADMetrics } from './metrics/base.js';\nimport { DeferredReadableStream } from './stream/deferred_stream.js';\nimport { IdentityTransform } from './stream/identity_transform.js';\n\nexport enum VADEventType {\n START_OF_SPEECH,\n INFERENCE_DONE,\n END_OF_SPEECH,\n METRICS_COLLECTED,\n}\n\nexport interface VADEvent {\n /** Type of the VAD event (e.g., start of speech, end of speech, inference done). */\n type: VADEventType;\n /**\n * Index of the audio sample where the event occurred, relative to the inference sample rate.\n */\n samplesIndex: number;\n /** Timestamp when the event was fired. */\n timestamp: number;\n /** Duration of the speech segment. */\n speechDuration: number;\n /** Duration of the silence segment. */\n silenceDuration: number;\n /**\n * List of audio frames associated with the speech.\n *\n * @remarks\n * - For `start_of_speech` events, this contains the audio chunks that triggered the detection.\n * - For `inference_done` events, this contains the audio chunks that were processed.\n * - For `end_of_speech` events, this contains the complete user speech.\n */\n frames: AudioFrame[];\n /** Probability that speech is present (only for `INFERENCE_DONE` events). */\n probability: number;\n /** Time taken to perform the inference, in seconds (only for `INFERENCE_DONE` events). */\n inferenceDuration: number;\n /** Indicates whether speech was detected in the frames. */\n speaking: boolean;\n /** Threshold used to detect silence. */\n rawAccumulatedSilence: number;\n /** Threshold used to detect speech. */\n rawAccumulatedSpeech: number;\n}\n\nexport interface VADCapabilities {\n updateInterval: number;\n}\n\nexport type VADCallbacks = {\n ['metrics_collected']: (metrics: VADMetrics) => void;\n};\n\nexport abstract class VAD extends (EventEmitter as new () => TypedEmitter<VADCallbacks>) {\n #capabilities: VADCapabilities;\n abstract label: string;\n\n constructor(capabilities: VADCapabilities) {\n super();\n this.#capabilities = capabilities;\n }\n\n get capabilities(): VADCapabilities {\n return this.#capabilities;\n }\n\n /**\n * Returns a {@link VADStream} that can be used to push audio frames and receive VAD events.\n */\n abstract stream(): VADStream;\n}\n\nexport abstract class VADStream implements AsyncIterableIterator<VADEvent> {\n protected static readonly FLUSH_SENTINEL = Symbol('FLUSH_SENTINEL');\n protected input = new IdentityTransform<AudioFrame | typeof VADStream.FLUSH_SENTINEL>();\n protected output = new IdentityTransform<VADEvent>();\n protected inputWriter: WritableStreamDefaultWriter<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected inputReader: ReadableStreamDefaultReader<AudioFrame | typeof VADStream.FLUSH_SENTINEL>;\n protected outputWriter: WritableStreamDefaultWriter<VADEvent>;\n protected outputReader: ReadableStreamDefaultReader<VADEvent>;\n protected closed = false;\n protected inputClosed = false;\n\n #vad: VAD;\n #lastActivityTime = BigInt(0);\n private logger = log();\n private deferredInputStream: DeferredReadableStream<AudioFrame>;\n\n private metricsStream: ReadableStream<VADEvent>;\n constructor(vad: VAD) {\n this.#vad = vad;\n this.deferredInputStream = new DeferredReadableStream<AudioFrame>();\n\n this.inputWriter = this.input.writable.getWriter();\n this.inputReader = this.input.readable.getReader();\n this.outputWriter = this.output.writable.getWriter();\n\n const [outputStream, metricsStream] = this.output.readable.tee();\n this.metricsStream = metricsStream;\n this.outputReader = outputStream.getReader();\n\n this.pumpDeferredStream();\n this.monitorMetrics();\n }\n\n /**\n * Reads from the deferred input stream and forwards chunks to the input writer.\n *\n * Note: we can't just do this.deferredInputStream.stream.pipeTo(this.input.writable)\n * because the inputWriter locks the this.input.writable stream. All writes must go through\n * the inputWriter.\n */\n private async pumpDeferredStream() {\n const reader = this.deferredInputStream.stream.getReader();\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n await this.inputWriter.write(value);\n }\n } catch (e) {\n this.logger.error(`Error pumping deferred stream: ${e}`);\n throw e;\n } finally {\n reader.releaseLock();\n }\n }\n\n protected async monitorMetrics() {\n let inferenceDurationTotalMs = 0;\n let inferenceCount = 0;\n const metricsReader = this.metricsStream.getReader();\n while (true) {\n const { done, value } = await metricsReader.read();\n if (done) {\n break;\n }\n switch (value.type) {\n case VADEventType.START_OF_SPEECH:\n inferenceCount++;\n if (inferenceCount >= 1 / this.#vad.capabilities.updateInterval) {\n this.#vad.emit('metrics_collected', {\n type: 'vad_metrics',\n timestamp: Date.now(),\n idleTimeMs: Math.trunc(\n Number((process.hrtime.bigint() - this.#lastActivityTime) / BigInt(1000000)),\n ),\n inferenceDurationTotalMs,\n inferenceCount,\n label: this.#vad.label,\n });\n\n inferenceCount = 0;\n inferenceDurationTotalMs = 0;\n }\n break;\n case VADEventType.INFERENCE_DONE:\n inferenceDurationTotalMs += Math.round(value.inferenceDuration);\n this.#lastActivityTime = process.hrtime.bigint();\n break;\n case VADEventType.END_OF_SPEECH:\n this.#lastActivityTime = process.hrtime.bigint();\n break;\n }\n }\n }\n\n /**\n * Safely send a VAD event to the output stream, handling writer release errors during shutdown.\n * @returns true if the event was sent, false if the stream is closing\n * @throws Error if an unexpected error occurs\n */\n protected sendVADEvent(event: VADEvent): boolean {\n if (this.closed) {\n return false;\n }\n\n try {\n this.outputWriter.write(event);\n return true;\n } catch (e) {\n throw e;\n }\n }\n\n updateInputStream(audioStream: ReadableStream<AudioFrame>) {\n this.deferredInputStream.setSource(audioStream);\n }\n\n detachInputStream() {\n this.deferredInputStream.detachSource();\n }\n\n /** @deprecated Use `updateInputStream` instead */\n pushFrame(frame: AudioFrame) {\n // TODO(AJS-395): remove this method\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(frame);\n }\n\n flush() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputWriter.write(VADStream.FLUSH_SENTINEL);\n }\n\n endInput() {\n if (this.inputClosed) {\n throw new Error('Input is closed');\n }\n if (this.closed) {\n throw new Error('Stream is closed');\n }\n this.inputClosed = true;\n this.input.writable.close();\n }\n\n async next(): Promise<IteratorResult<VADEvent>> {\n return this.outputReader.read().then(({ done, value }) => {\n if (done) {\n return { done: true, value: undefined };\n }\n return { done: false, value };\n });\n }\n\n close() {\n this.outputWriter.releaseLock();\n this.outputReader.cancel();\n this.output.writable.close();\n this.closed = true;\n }\n\n [Symbol.asyncIterator](): VADStream {\n return this;\n }\n}\n"],"mappings":"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","names":["VADEventType"]}