@livekit/agents 1.0.35 → 1.0.36-dev.0

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 (131) hide show
  1. package/dist/index.cjs +3 -1
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/inference/interruption/AdaptiveInterruptionDetector.cjs +152 -0
  9. package/dist/inference/interruption/AdaptiveInterruptionDetector.cjs.map +1 -0
  10. package/dist/inference/interruption/AdaptiveInterruptionDetector.d.cts +50 -0
  11. package/dist/inference/interruption/AdaptiveInterruptionDetector.d.ts +50 -0
  12. package/dist/inference/interruption/AdaptiveInterruptionDetector.d.ts.map +1 -0
  13. package/dist/inference/interruption/AdaptiveInterruptionDetector.js +125 -0
  14. package/dist/inference/interruption/AdaptiveInterruptionDetector.js.map +1 -0
  15. package/dist/inference/interruption/InterruptionStream.cjs +310 -0
  16. package/dist/inference/interruption/InterruptionStream.cjs.map +1 -0
  17. package/dist/inference/interruption/InterruptionStream.d.cts +57 -0
  18. package/dist/inference/interruption/InterruptionStream.d.ts +57 -0
  19. package/dist/inference/interruption/InterruptionStream.d.ts.map +1 -0
  20. package/dist/inference/interruption/InterruptionStream.js +288 -0
  21. package/dist/inference/interruption/InterruptionStream.js.map +1 -0
  22. package/dist/inference/interruption/defaults.cjs +76 -0
  23. package/dist/inference/interruption/defaults.cjs.map +1 -0
  24. package/dist/inference/interruption/defaults.d.cts +14 -0
  25. package/dist/inference/interruption/defaults.d.ts +14 -0
  26. package/dist/inference/interruption/defaults.d.ts.map +1 -0
  27. package/dist/inference/interruption/defaults.js +42 -0
  28. package/dist/inference/interruption/defaults.js.map +1 -0
  29. package/dist/inference/interruption/errors.cjs +2 -0
  30. package/dist/inference/interruption/errors.cjs.map +1 -0
  31. package/dist/inference/interruption/errors.d.cts +2 -0
  32. package/dist/inference/interruption/errors.d.ts +2 -0
  33. package/dist/inference/interruption/errors.d.ts.map +1 -0
  34. package/dist/inference/interruption/errors.js +1 -0
  35. package/dist/inference/interruption/errors.js.map +1 -0
  36. package/dist/inference/interruption/http_transport.cjs +57 -0
  37. package/dist/inference/interruption/http_transport.cjs.map +1 -0
  38. package/dist/inference/interruption/http_transport.d.cts +23 -0
  39. package/dist/inference/interruption/http_transport.d.ts +23 -0
  40. package/dist/inference/interruption/http_transport.d.ts.map +1 -0
  41. package/dist/inference/interruption/http_transport.js +33 -0
  42. package/dist/inference/interruption/http_transport.js.map +1 -0
  43. package/dist/inference/interruption/index.cjs +34 -0
  44. package/dist/inference/interruption/index.cjs.map +1 -0
  45. package/dist/inference/interruption/index.d.cts +5 -0
  46. package/dist/inference/interruption/index.d.ts +5 -0
  47. package/dist/inference/interruption/index.d.ts.map +1 -0
  48. package/dist/inference/interruption/index.js +7 -0
  49. package/dist/inference/interruption/index.js.map +1 -0
  50. package/dist/inference/interruption/interruption.cjs +85 -0
  51. package/dist/inference/interruption/interruption.cjs.map +1 -0
  52. package/dist/inference/interruption/interruption.d.cts +48 -0
  53. package/dist/inference/interruption/interruption.d.ts +48 -0
  54. package/dist/inference/interruption/interruption.d.ts.map +1 -0
  55. package/dist/inference/interruption/interruption.js +59 -0
  56. package/dist/inference/interruption/interruption.js.map +1 -0
  57. package/dist/inference/utils.cjs +15 -2
  58. package/dist/inference/utils.cjs.map +1 -1
  59. package/dist/inference/utils.d.cts +1 -0
  60. package/dist/inference/utils.d.ts +1 -0
  61. package/dist/inference/utils.d.ts.map +1 -1
  62. package/dist/inference/utils.js +13 -1
  63. package/dist/inference/utils.js.map +1 -1
  64. package/dist/inference/utils.test.cjs +20 -0
  65. package/dist/inference/utils.test.cjs.map +1 -0
  66. package/dist/inference/utils.test.js +19 -0
  67. package/dist/inference/utils.test.js.map +1 -0
  68. package/dist/stream/stream_channel.cjs +3 -0
  69. package/dist/stream/stream_channel.cjs.map +1 -1
  70. package/dist/stream/stream_channel.d.cts +3 -2
  71. package/dist/stream/stream_channel.d.ts +3 -2
  72. package/dist/stream/stream_channel.d.ts.map +1 -1
  73. package/dist/stream/stream_channel.js +3 -0
  74. package/dist/stream/stream_channel.js.map +1 -1
  75. package/dist/telemetry/trace_types.cjs +15 -0
  76. package/dist/telemetry/trace_types.cjs.map +1 -1
  77. package/dist/telemetry/trace_types.d.cts +5 -0
  78. package/dist/telemetry/trace_types.d.ts +5 -0
  79. package/dist/telemetry/trace_types.d.ts.map +1 -1
  80. package/dist/telemetry/trace_types.js +10 -0
  81. package/dist/telemetry/trace_types.js.map +1 -1
  82. package/dist/utils/ws_transport.cjs +51 -0
  83. package/dist/utils/ws_transport.cjs.map +1 -0
  84. package/dist/utils/ws_transport.d.cts +9 -0
  85. package/dist/utils/ws_transport.d.ts +9 -0
  86. package/dist/utils/ws_transport.d.ts.map +1 -0
  87. package/dist/utils/ws_transport.js +17 -0
  88. package/dist/utils/ws_transport.js.map +1 -0
  89. package/dist/utils/ws_transport.test.cjs +212 -0
  90. package/dist/utils/ws_transport.test.cjs.map +1 -0
  91. package/dist/utils/ws_transport.test.js +211 -0
  92. package/dist/utils/ws_transport.test.js.map +1 -0
  93. package/dist/voice/agent_activity.cjs +49 -0
  94. package/dist/voice/agent_activity.cjs.map +1 -1
  95. package/dist/voice/agent_activity.d.cts +14 -0
  96. package/dist/voice/agent_activity.d.ts +14 -0
  97. package/dist/voice/agent_activity.d.ts.map +1 -1
  98. package/dist/voice/agent_activity.js +49 -0
  99. package/dist/voice/agent_activity.js.map +1 -1
  100. package/dist/voice/agent_session.cjs +12 -1
  101. package/dist/voice/agent_session.cjs.map +1 -1
  102. package/dist/voice/agent_session.d.cts +3 -0
  103. package/dist/voice/agent_session.d.ts +3 -0
  104. package/dist/voice/agent_session.d.ts.map +1 -1
  105. package/dist/voice/agent_session.js +12 -1
  106. package/dist/voice/agent_session.js.map +1 -1
  107. package/dist/voice/audio_recognition.cjs +124 -2
  108. package/dist/voice/audio_recognition.cjs.map +1 -1
  109. package/dist/voice/audio_recognition.d.cts +32 -1
  110. package/dist/voice/audio_recognition.d.ts +32 -1
  111. package/dist/voice/audio_recognition.d.ts.map +1 -1
  112. package/dist/voice/audio_recognition.js +127 -2
  113. package/dist/voice/audio_recognition.js.map +1 -1
  114. package/package.json +2 -1
  115. package/src/index.ts +2 -0
  116. package/src/inference/interruption/AdaptiveInterruptionDetector.ts +166 -0
  117. package/src/inference/interruption/InterruptionStream.ts +397 -0
  118. package/src/inference/interruption/defaults.ts +33 -0
  119. package/src/inference/interruption/errors.ts +0 -0
  120. package/src/inference/interruption/http_transport.ts +61 -0
  121. package/src/inference/interruption/index.ts +4 -0
  122. package/src/inference/interruption/interruption.ts +88 -0
  123. package/src/inference/utils.test.ts +31 -0
  124. package/src/inference/utils.ts +15 -0
  125. package/src/stream/stream_channel.ts +6 -2
  126. package/src/telemetry/trace_types.ts +7 -0
  127. package/src/utils/ws_transport.test.ts +282 -0
  128. package/src/utils/ws_transport.ts +22 -0
  129. package/src/voice/agent_activity.ts +61 -0
  130. package/src/voice/agent_session.ts +22 -2
  131. package/src/voice/audio_recognition.ts +161 -1
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var InterruptionStream_exports = {};
20
+ __export(InterruptionStream_exports, {
21
+ InterruptionStreamBase: () => InterruptionStreamBase,
22
+ InterruptionStreamSentinel: () => InterruptionStreamSentinel
23
+ });
24
+ module.exports = __toCommonJS(InterruptionStream_exports);
25
+ var import_rtc_node = require("@livekit/rtc-node");
26
+ var import_telemetry = require("../../telemetry/index.cjs");
27
+ var import_web = require("stream/web");
28
+ var import_log = require("../../log.cjs");
29
+ var import_stream_channel = require("../../stream/stream_channel.cjs");
30
+ var import_utils = require("../utils.cjs");
31
+ var import_defaults = require("./defaults.cjs");
32
+ var import_http_transport = require("./http_transport.cjs");
33
+ var import_interruption = require("./interruption.cjs");
34
+ class InterruptionStreamSentinel {
35
+ static speechStarted() {
36
+ return { type: "agent-speech-started" };
37
+ }
38
+ static speechEnded() {
39
+ return { type: "agent-speech-ended" };
40
+ }
41
+ static overlapSpeechStarted(speechDuration, userSpeakingSpan) {
42
+ return { type: "overlap-speech-started", speechDuration, userSpeakingSpan };
43
+ }
44
+ static overlapSpeechEnded() {
45
+ return { type: "overlap-speech-ended" };
46
+ }
47
+ static flush() {
48
+ return { type: "flush" };
49
+ }
50
+ }
51
+ function updateUserSpeakingSpan(span, entry) {
52
+ span.setAttribute(
53
+ import_telemetry.traceTypes.ATTR_IS_INTERRUPTION,
54
+ (entry.isInterruption ?? false).toString().toLowerCase()
55
+ );
56
+ span.setAttribute(import_telemetry.traceTypes.ATTR_INTERRUPTION_PROBABILITY, entry.probability);
57
+ span.setAttribute(import_telemetry.traceTypes.ATTR_INTERRUPTION_TOTAL_DURATION, entry.totalDuration);
58
+ span.setAttribute(import_telemetry.traceTypes.ATTR_INTERRUPTION_PREDICTION_DURATION, entry.predictionDuration);
59
+ span.setAttribute(import_telemetry.traceTypes.ATTR_INTERRUPTION_DETECTION_DELAY, entry.detectionDelay);
60
+ }
61
+ class InterruptionStreamBase {
62
+ inputStream;
63
+ eventStream;
64
+ resampler;
65
+ userSpeakingSpan;
66
+ overlapSpeechStartedAt;
67
+ options;
68
+ apiOptions;
69
+ model;
70
+ constructor(model, apiOptions) {
71
+ this.inputStream = (0, import_stream_channel.createStreamChannel)();
72
+ this.eventStream = (0, import_stream_channel.createStreamChannel)();
73
+ this.model = model;
74
+ this.options = model.options;
75
+ this.apiOptions = { ...import_defaults.apiConnectDefaults, ...apiOptions };
76
+ this.setupTransform();
77
+ }
78
+ setupTransform() {
79
+ let agentSpeechStarted = false;
80
+ let startIdx = 0;
81
+ let accumulatedSamples = 0;
82
+ let overlapSpeechStarted = false;
83
+ const cache = /* @__PURE__ */ new Map();
84
+ const inferenceS16Data = new Int16Array(
85
+ Math.ceil(this.options.maxAudioDuration * this.options.sampleRate)
86
+ ).fill(0);
87
+ const transformer = new import_web.TransformStream(
88
+ {
89
+ transform: (chunk, controller) => {
90
+ if (chunk instanceof import_rtc_node.AudioFrame) {
91
+ if (!agentSpeechStarted) {
92
+ return;
93
+ }
94
+ if (this.options.sampleRate !== chunk.sampleRate) {
95
+ controller.error("the sample rate of the input frames must be consistent");
96
+ return;
97
+ }
98
+ const result = writeToInferenceS16Data(
99
+ chunk,
100
+ startIdx,
101
+ inferenceS16Data,
102
+ this.options.maxAudioDuration
103
+ );
104
+ startIdx = result.startIdx;
105
+ accumulatedSamples += result.samplesWritten;
106
+ if (accumulatedSamples >= Math.floor(this.options.detectionInterval * this.options.sampleRate) && overlapSpeechStarted) {
107
+ const audioSlice = inferenceS16Data.slice(0, startIdx);
108
+ accumulatedSamples = 0;
109
+ controller.enqueue(audioSlice);
110
+ }
111
+ } else if (chunk.type === "agent-speech-started") {
112
+ (0, import_log.log)().debug("agent speech started");
113
+ agentSpeechStarted = true;
114
+ overlapSpeechStarted = false;
115
+ accumulatedSamples = 0;
116
+ startIdx = 0;
117
+ cache.clear();
118
+ } else if (chunk.type === "agent-speech-ended") {
119
+ (0, import_log.log)().debug("agent speech ended");
120
+ agentSpeechStarted = false;
121
+ overlapSpeechStarted = false;
122
+ accumulatedSamples = 0;
123
+ startIdx = 0;
124
+ cache.clear();
125
+ } else if (chunk.type === "overlap-speech-started" && agentSpeechStarted) {
126
+ this.userSpeakingSpan = chunk.userSpeakingSpan;
127
+ (0, import_log.log)().debug("overlap speech started, starting interruption inference");
128
+ overlapSpeechStarted = true;
129
+ accumulatedSamples = 0;
130
+ const shiftSize = Math.min(
131
+ startIdx,
132
+ Math.round(chunk.speechDuration * this.options.sampleRate) + Math.round(this.options.audioPrefixDuration * this.options.sampleRate)
133
+ );
134
+ inferenceS16Data.copyWithin(0, startIdx - shiftSize, startIdx);
135
+ startIdx = shiftSize;
136
+ cache.clear();
137
+ } else if (chunk.type === "overlap-speech-ended") {
138
+ (0, import_log.log)().debug("overlap speech ended");
139
+ if (overlapSpeechStarted) {
140
+ this.userSpeakingSpan = void 0;
141
+ let latestEntry = Array.from(cache.values()).at(-1);
142
+ if (!latestEntry) {
143
+ (0, import_log.log)().debug("no request made for overlap speech");
144
+ latestEntry = import_interruption.InterruptionCacheEntry.default();
145
+ } else {
146
+ cache.delete(latestEntry.createdAt);
147
+ }
148
+ const event = {
149
+ type: import_interruption.InterruptionEventType.OVERLAP_SPEECH_ENDED,
150
+ timestamp: Date.now(),
151
+ isInterruption: false,
152
+ overlapSpeechStartedAt: this.overlapSpeechStartedAt,
153
+ speechInput: latestEntry.speechInput,
154
+ probabilities: latestEntry.probabilities,
155
+ totalDuration: latestEntry.totalDuration,
156
+ detectionDelay: latestEntry.detectionDelay,
157
+ predictionDuration: latestEntry.predictionDuration,
158
+ probability: latestEntry.probability
159
+ };
160
+ this.eventStream.write(event);
161
+ }
162
+ } else if (chunk.type === "flush") {
163
+ (0, import_log.log)().debug("flushing");
164
+ }
165
+ }
166
+ },
167
+ { highWaterMark: Number.MAX_SAFE_INTEGER },
168
+ { highWaterMark: Number.MAX_SAFE_INTEGER }
169
+ );
170
+ const httpPostWriter = new import_web.WritableStream(
171
+ {
172
+ // Implement the sink
173
+ write: async (chunk) => {
174
+ if (!this.overlapSpeechStartedAt) {
175
+ return;
176
+ }
177
+ const resp = await (0, import_http_transport.predictHTTP)(
178
+ chunk,
179
+ { threshold: this.options.threshold, minFrames: this.options.minFrames },
180
+ {
181
+ baseUrl: this.options.baseUrl,
182
+ timeout: this.options.inferenceTimeout,
183
+ token: await (0, import_utils.createAccessToken)(this.options.apiKey, this.options.apiSecret)
184
+ }
185
+ );
186
+ console.log("received inference response", resp);
187
+ const { createdAt, isBargein, probabilities, predictionDuration } = resp;
188
+ const entry = new import_interruption.InterruptionCacheEntry({
189
+ createdAt,
190
+ probabilities,
191
+ isInterruption: isBargein,
192
+ speechInput: chunk,
193
+ totalDuration: (performance.now() - createdAt) / 1e9,
194
+ detectionDelay: Date.now() - this.overlapSpeechStartedAt,
195
+ predictionDuration
196
+ });
197
+ cache.set(createdAt, entry);
198
+ if (overlapSpeechStarted && entry.isInterruption) {
199
+ if (this.userSpeakingSpan) {
200
+ updateUserSpeakingSpan(this.userSpeakingSpan, entry);
201
+ }
202
+ const event = {
203
+ type: import_interruption.InterruptionEventType.INTERRUPTION,
204
+ timestamp: Date.now(),
205
+ overlapSpeechStartedAt: this.overlapSpeechStartedAt,
206
+ isInterruption: entry.isInterruption,
207
+ speechInput: entry.speechInput,
208
+ probabilities: entry.probabilities,
209
+ totalDuration: entry.totalDuration,
210
+ predictionDuration: entry.predictionDuration,
211
+ detectionDelay: entry.detectionDelay,
212
+ probability: entry.probability
213
+ };
214
+ this.eventStream.write(event);
215
+ }
216
+ },
217
+ close() {
218
+ console.log("closing http writer");
219
+ },
220
+ abort(err) {
221
+ console.log("Sink error:", err);
222
+ }
223
+ },
224
+ { highWaterMark: Number.MAX_SAFE_INTEGER }
225
+ );
226
+ this.inputStream.stream().pipeThrough(transformer).pipeTo(httpPostWriter);
227
+ }
228
+ ensureInputNotEnded() {
229
+ if (this.inputStream.closed) {
230
+ throw new Error("input stream is closed");
231
+ }
232
+ }
233
+ ensureStreamsNotEnded() {
234
+ this.ensureInputNotEnded();
235
+ }
236
+ getResamplerFor(inputSampleRate) {
237
+ if (!this.resampler) {
238
+ this.resampler = new import_rtc_node.AudioResampler(inputSampleRate, this.options.sampleRate);
239
+ }
240
+ return this.resampler;
241
+ }
242
+ get stream() {
243
+ return this.eventStream.stream();
244
+ }
245
+ async pushFrame(frame) {
246
+ this.ensureStreamsNotEnded();
247
+ if (!(frame instanceof import_rtc_node.AudioFrame)) {
248
+ if (frame.type === "overlap-speech-started") {
249
+ this.overlapSpeechStartedAt = Date.now() - frame.speechDuration;
250
+ }
251
+ return this.inputStream.write(frame);
252
+ } else if (this.options.sampleRate !== frame.sampleRate) {
253
+ const resampler = this.getResamplerFor(frame.sampleRate);
254
+ if (resampler.inputRate !== frame.sampleRate) {
255
+ throw new Error("the sample rate of the input frames must be consistent");
256
+ }
257
+ for (const resampledFrame of resampler.push(frame)) {
258
+ await this.inputStream.write(resampledFrame);
259
+ }
260
+ } else {
261
+ await this.inputStream.write(frame);
262
+ }
263
+ }
264
+ async flush() {
265
+ this.ensureStreamsNotEnded();
266
+ this.inputStream.write(InterruptionStreamSentinel.flush());
267
+ }
268
+ async endInput() {
269
+ await this.flush();
270
+ await this.inputStream.close();
271
+ }
272
+ async close() {
273
+ if (!this.inputStream.closed) await this.inputStream.close();
274
+ }
275
+ }
276
+ function writeToInferenceS16Data(frame, startIdx, outData, maxAudioDuration) {
277
+ const maxWindowSize = Math.floor(maxAudioDuration * frame.sampleRate);
278
+ if (frame.samplesPerChannel > outData.length) {
279
+ throw new Error("frame samples are greater than the max window size");
280
+ }
281
+ const shift = startIdx + frame.samplesPerChannel - maxWindowSize;
282
+ if (shift > 0) {
283
+ outData.copyWithin(0, shift, startIdx);
284
+ startIdx -= shift;
285
+ }
286
+ const frameData = new Int16Array(
287
+ frame.data.buffer,
288
+ frame.data.byteOffset,
289
+ frame.samplesPerChannel * frame.channels
290
+ );
291
+ if (frame.channels > 1) {
292
+ for (let i = 0; i < frame.samplesPerChannel; i++) {
293
+ let sum = 0;
294
+ for (let ch = 0; ch < frame.channels; ch++) {
295
+ sum += frameData[i * frame.channels + ch] ?? 0;
296
+ }
297
+ outData[startIdx + i] = Math.floor(sum / frame.channels);
298
+ }
299
+ } else {
300
+ outData.set(frameData, startIdx);
301
+ }
302
+ startIdx += frame.samplesPerChannel;
303
+ return { startIdx, samplesWritten: frame.samplesPerChannel };
304
+ }
305
+ // Annotate the CommonJS export names for ESM import in node:
306
+ 0 && (module.exports = {
307
+ InterruptionStreamBase,
308
+ InterruptionStreamSentinel
309
+ });
310
+ //# sourceMappingURL=InterruptionStream.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/inference/interruption/InterruptionStream.ts"],"sourcesContent":["import { AudioFrame, AudioResampler } from '@livekit/rtc-node';\nimport type { Span } from '@opentelemetry/api';\nimport { traceTypes } from '../../telemetry/index.js';\nimport { type ReadableStream, TransformStream, WritableStream } from 'stream/web';\nimport { log } from '../../log.js';\nimport { type StreamChannel, createStreamChannel } from '../../stream/stream_channel.js';\nimport { createAccessToken } from '../utils.js';\nimport type {\n AdaptiveInterruptionDetector,\n InterruptionOptions,\n} from './AdaptiveInterruptionDetector.js';\nimport { apiConnectDefaults } from './defaults.js';\nimport { predictHTTP } from './http_transport.js';\nimport {\n InterruptionCacheEntry,\n type InterruptionDetectionError,\n type InterruptionEvent,\n InterruptionEventType,\n} from './interruption.js';\n\nexport interface AgentSpeechStarted {\n type: 'agent-speech-started';\n}\n\nexport interface AgentSpeechEnded {\n type: 'agent-speech-ended';\n}\n\nexport interface OverlapSpeechStarted {\n type: 'overlap-speech-started';\n speechDuration: number;\n userSpeakingSpan: Span;\n}\n\nexport interface OverlapSpeechEnded {\n type: 'overlap-speech-ended';\n}\n\nexport interface Flush {\n type: 'flush';\n}\n\nexport type InterruptionSentinel =\n | AgentSpeechStarted\n | AgentSpeechEnded\n | OverlapSpeechStarted\n | OverlapSpeechEnded\n | Flush;\n\nexport class InterruptionStreamSentinel {\n static speechStarted(): AgentSpeechStarted {\n return { type: 'agent-speech-started' };\n }\n\n static speechEnded(): AgentSpeechEnded {\n return { type: 'agent-speech-ended' };\n }\n\n static overlapSpeechStarted(\n speechDuration: number,\n userSpeakingSpan: Span,\n ): OverlapSpeechStarted {\n return { type: 'overlap-speech-started', speechDuration, userSpeakingSpan };\n }\n\n static overlapSpeechEnded(): OverlapSpeechEnded {\n return { type: 'overlap-speech-ended' };\n }\n\n static flush(): Flush {\n return { type: 'flush' };\n }\n}\n\nexport interface ApiConnectOptions {\n maxRetries: number;\n retryInterval: number;\n timeout: number;\n}\n\nfunction updateUserSpeakingSpan(span: Span, entry: InterruptionCacheEntry) {\n span.setAttribute(\n traceTypes.ATTR_IS_INTERRUPTION,\n (entry.isInterruption ?? false).toString().toLowerCase(),\n );\n span.setAttribute(traceTypes.ATTR_INTERRUPTION_PROBABILITY, entry.probability);\n span.setAttribute(traceTypes.ATTR_INTERRUPTION_TOTAL_DURATION, entry.totalDuration);\n span.setAttribute(traceTypes.ATTR_INTERRUPTION_PREDICTION_DURATION, entry.predictionDuration);\n span.setAttribute(traceTypes.ATTR_INTERRUPTION_DETECTION_DELAY, entry.detectionDelay);\n}\n\nexport class InterruptionStreamBase {\n private inputStream: StreamChannel<InterruptionSentinel | AudioFrame, InterruptionDetectionError>;\n\n private eventStream: StreamChannel<InterruptionEvent, InterruptionDetectionError>;\n\n private resampler?: AudioResampler;\n\n private userSpeakingSpan: Span | undefined;\n\n private overlapSpeechStartedAt: number | undefined;\n\n private options: InterruptionOptions;\n\n private apiOptions: ApiConnectOptions;\n\n private model: AdaptiveInterruptionDetector;\n\n constructor(model: AdaptiveInterruptionDetector, apiOptions: Partial<ApiConnectOptions>) {\n this.inputStream = createStreamChannel<\n InterruptionSentinel | AudioFrame,\n InterruptionDetectionError\n >();\n\n this.eventStream = createStreamChannel<InterruptionEvent, InterruptionDetectionError>();\n\n this.model = model;\n this.options = model.options;\n this.apiOptions = { ...apiConnectDefaults, ...apiOptions };\n\n this.setupTransform();\n }\n\n private setupTransform() {\n let agentSpeechStarted = false;\n let startIdx = 0;\n let accumulatedSamples = 0;\n let overlapSpeechStarted = false;\n const cache = new Map<number, InterruptionCacheEntry>(); // TODO limit cache size\n const inferenceS16Data = new Int16Array(\n Math.ceil(this.options.maxAudioDuration * this.options.sampleRate),\n ).fill(0);\n\n const transformer = new TransformStream<InterruptionSentinel | AudioFrame, Int16Array>(\n {\n transform: (chunk, controller) => {\n if (chunk instanceof AudioFrame) {\n if (!agentSpeechStarted) {\n return;\n }\n if (this.options.sampleRate !== chunk.sampleRate) {\n controller.error('the sample rate of the input frames must be consistent');\n return;\n }\n const result = writeToInferenceS16Data(\n chunk,\n startIdx,\n inferenceS16Data,\n this.options.maxAudioDuration,\n );\n startIdx = result.startIdx;\n accumulatedSamples += result.samplesWritten;\n\n // Send data for inference when enough samples accumulated during overlap\n if (\n accumulatedSamples >=\n Math.floor(this.options.detectionInterval * this.options.sampleRate) &&\n overlapSpeechStarted\n ) {\n // Send a copy of the audio data up to startIdx for inference\n const audioSlice = inferenceS16Data.slice(0, startIdx);\n // TODO: send to data channel - dataChan.send(audioSlice);\n accumulatedSamples = 0;\n controller.enqueue(audioSlice);\n }\n } else if (chunk.type === 'agent-speech-started') {\n log().debug('agent speech started');\n\n agentSpeechStarted = true;\n overlapSpeechStarted = false;\n accumulatedSamples = 0;\n startIdx = 0;\n cache.clear();\n } else if (chunk.type === 'agent-speech-ended') {\n log().debug('agent speech ended');\n\n agentSpeechStarted = false;\n overlapSpeechStarted = false;\n accumulatedSamples = 0;\n startIdx = 0;\n cache.clear();\n } else if (chunk.type === 'overlap-speech-started' && agentSpeechStarted) {\n this.userSpeakingSpan = chunk.userSpeakingSpan;\n log().debug('overlap speech started, starting interruption inference');\n overlapSpeechStarted = true;\n accumulatedSamples = 0;\n // Include both speech duration and audio prefix duration for context\n const shiftSize = Math.min(\n startIdx,\n Math.round(chunk.speechDuration * this.options.sampleRate) +\n Math.round(this.options.audioPrefixDuration * this.options.sampleRate),\n );\n // Shift the buffer: copy the last `shiftSize` samples before startIdx\n // to the beginning of the buffer. This preserves recent audio context\n // (the user's speech that occurred just before overlap was detected).\n inferenceS16Data.copyWithin(0, startIdx - shiftSize, startIdx);\n startIdx = shiftSize;\n cache.clear();\n } else if (chunk.type === 'overlap-speech-ended') {\n log().debug('overlap speech ended');\n\n if (overlapSpeechStarted) {\n this.userSpeakingSpan = undefined;\n let latestEntry = Array.from(cache.values()).at(-1);\n if (!latestEntry) {\n log().debug('no request made for overlap speech');\n latestEntry = InterruptionCacheEntry.default();\n } else {\n cache.delete(latestEntry.createdAt);\n }\n const event: InterruptionEvent = {\n type: InterruptionEventType.OVERLAP_SPEECH_ENDED,\n timestamp: Date.now(),\n isInterruption: false,\n overlapSpeechStartedAt: this.overlapSpeechStartedAt,\n speechInput: latestEntry.speechInput,\n probabilities: latestEntry.probabilities,\n totalDuration: latestEntry.totalDuration,\n detectionDelay: latestEntry.detectionDelay,\n predictionDuration: latestEntry.predictionDuration,\n probability: latestEntry.probability,\n };\n this.eventStream.write(event);\n }\n } else if (chunk.type === 'flush') {\n log().debug('flushing');\n // do nothing\n }\n },\n },\n { highWaterMark: Number.MAX_SAFE_INTEGER },\n { highWaterMark: Number.MAX_SAFE_INTEGER },\n );\n\n const httpPostWriter = new WritableStream<Int16Array>(\n {\n // Implement the sink\n write: async (chunk) => {\n if (!this.overlapSpeechStartedAt) {\n return;\n }\n const resp = await predictHTTP(\n chunk,\n { threshold: this.options.threshold, minFrames: this.options.minFrames },\n {\n baseUrl: this.options.baseUrl,\n timeout: this.options.inferenceTimeout,\n token: await createAccessToken(this.options.apiKey, this.options.apiSecret),\n },\n );\n console.log('received inference response', resp);\n const { createdAt, isBargein, probabilities, predictionDuration } = resp;\n const entry = new InterruptionCacheEntry({\n createdAt,\n probabilities,\n isInterruption: isBargein,\n speechInput: chunk,\n totalDuration: (performance.now() - createdAt) / 1e9,\n detectionDelay: Date.now() - this.overlapSpeechStartedAt,\n predictionDuration,\n });\n cache.set(createdAt, entry);\n if (overlapSpeechStarted && entry.isInterruption) {\n if (this.userSpeakingSpan) {\n updateUserSpeakingSpan(this.userSpeakingSpan, entry);\n }\n const event: InterruptionEvent = {\n type: InterruptionEventType.INTERRUPTION,\n timestamp: Date.now(),\n overlapSpeechStartedAt: this.overlapSpeechStartedAt,\n isInterruption: entry.isInterruption,\n speechInput: entry.speechInput,\n probabilities: entry.probabilities,\n totalDuration: entry.totalDuration,\n predictionDuration: entry.predictionDuration,\n detectionDelay: entry.detectionDelay,\n probability: entry.probability,\n };\n this.eventStream.write(event);\n }\n },\n close() {\n console.log('closing http writer');\n },\n abort(err) {\n console.log('Sink error:', err);\n },\n },\n { highWaterMark: Number.MAX_SAFE_INTEGER },\n );\n\n this.inputStream.stream().pipeThrough(transformer).pipeTo(httpPostWriter);\n }\n\n private ensureInputNotEnded() {\n if (this.inputStream.closed) {\n throw new Error('input stream is closed');\n }\n }\n\n private ensureStreamsNotEnded() {\n this.ensureInputNotEnded();\n }\n\n private getResamplerFor(inputSampleRate: number): AudioResampler {\n if (!this.resampler) {\n this.resampler = new AudioResampler(inputSampleRate, this.options.sampleRate);\n }\n return this.resampler;\n }\n\n get stream(): ReadableStream<InterruptionEvent> {\n return this.eventStream.stream();\n }\n\n async pushFrame(frame: InterruptionSentinel | AudioFrame): Promise<void> {\n this.ensureStreamsNotEnded();\n if (!(frame instanceof AudioFrame)) {\n if (frame.type === 'overlap-speech-started') {\n this.overlapSpeechStartedAt = Date.now() - frame.speechDuration;\n }\n return this.inputStream.write(frame);\n } else if (this.options.sampleRate !== frame.sampleRate) {\n const resampler = this.getResamplerFor(frame.sampleRate);\n if (resampler.inputRate !== frame.sampleRate) {\n throw new Error('the sample rate of the input frames must be consistent');\n }\n for (const resampledFrame of resampler.push(frame)) {\n await this.inputStream.write(resampledFrame);\n }\n } else {\n await this.inputStream.write(frame);\n }\n }\n\n async flush(): Promise<void> {\n this.ensureStreamsNotEnded();\n this.inputStream.write(InterruptionStreamSentinel.flush());\n }\n\n async endInput(): Promise<void> {\n await this.flush();\n await this.inputStream.close();\n }\n\n async close(): Promise<void> {\n if (!this.inputStream.closed) await this.inputStream.close();\n }\n}\n\n/**\n * Write the audio frame to the output data array and return the new start index\n * and the number of samples written.\n */\nfunction writeToInferenceS16Data(\n frame: AudioFrame,\n startIdx: number,\n outData: Int16Array,\n maxAudioDuration: number,\n): { startIdx: number; samplesWritten: number } {\n const maxWindowSize = Math.floor(maxAudioDuration * frame.sampleRate);\n\n if (frame.samplesPerChannel > outData.length) {\n throw new Error('frame samples are greater than the max window size');\n }\n\n // Shift the data to the left if the window would overflow\n const shift = startIdx + frame.samplesPerChannel - maxWindowSize;\n if (shift > 0) {\n outData.copyWithin(0, shift, startIdx);\n startIdx -= shift;\n }\n\n // Get the frame data as Int16Array\n const frameData = new Int16Array(\n frame.data.buffer,\n frame.data.byteOffset,\n frame.samplesPerChannel * frame.channels,\n );\n\n if (frame.channels > 1) {\n // Mix down multiple channels to mono by averaging\n for (let i = 0; i < frame.samplesPerChannel; i++) {\n let sum = 0;\n for (let ch = 0; ch < frame.channels; ch++) {\n sum += frameData[i * frame.channels + ch] ?? 0;\n }\n outData[startIdx + i] = Math.floor(sum / frame.channels);\n }\n } else {\n // Single channel - copy directly\n outData.set(frameData, startIdx);\n }\n\n startIdx += frame.samplesPerChannel;\n return { startIdx, samplesWritten: frame.samplesPerChannel };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,57 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { AudioFrame } from '@livekit/rtc-node';
3
+ import type { Span } from '@opentelemetry/api';
4
+ import { type ReadableStream } from 'stream/web';
5
+ import type { AdaptiveInterruptionDetector } from './AdaptiveInterruptionDetector.js';
6
+ import { type InterruptionEvent } from './interruption.js';
7
+ export interface AgentSpeechStarted {
8
+ type: 'agent-speech-started';
9
+ }
10
+ export interface AgentSpeechEnded {
11
+ type: 'agent-speech-ended';
12
+ }
13
+ export interface OverlapSpeechStarted {
14
+ type: 'overlap-speech-started';
15
+ speechDuration: number;
16
+ userSpeakingSpan: Span;
17
+ }
18
+ export interface OverlapSpeechEnded {
19
+ type: 'overlap-speech-ended';
20
+ }
21
+ export interface Flush {
22
+ type: 'flush';
23
+ }
24
+ export type InterruptionSentinel = AgentSpeechStarted | AgentSpeechEnded | OverlapSpeechStarted | OverlapSpeechEnded | Flush;
25
+ export declare class InterruptionStreamSentinel {
26
+ static speechStarted(): AgentSpeechStarted;
27
+ static speechEnded(): AgentSpeechEnded;
28
+ static overlapSpeechStarted(speechDuration: number, userSpeakingSpan: Span): OverlapSpeechStarted;
29
+ static overlapSpeechEnded(): OverlapSpeechEnded;
30
+ static flush(): Flush;
31
+ }
32
+ export interface ApiConnectOptions {
33
+ maxRetries: number;
34
+ retryInterval: number;
35
+ timeout: number;
36
+ }
37
+ export declare class InterruptionStreamBase {
38
+ private inputStream;
39
+ private eventStream;
40
+ private resampler?;
41
+ private userSpeakingSpan;
42
+ private overlapSpeechStartedAt;
43
+ private options;
44
+ private apiOptions;
45
+ private model;
46
+ constructor(model: AdaptiveInterruptionDetector, apiOptions: Partial<ApiConnectOptions>);
47
+ private setupTransform;
48
+ private ensureInputNotEnded;
49
+ private ensureStreamsNotEnded;
50
+ private getResamplerFor;
51
+ get stream(): ReadableStream<InterruptionEvent>;
52
+ pushFrame(frame: InterruptionSentinel | AudioFrame): Promise<void>;
53
+ flush(): Promise<void>;
54
+ endInput(): Promise<void>;
55
+ close(): Promise<void>;
56
+ }
57
+ //# sourceMappingURL=InterruptionStream.d.ts.map
@@ -0,0 +1,57 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { AudioFrame } from '@livekit/rtc-node';
3
+ import type { Span } from '@opentelemetry/api';
4
+ import { type ReadableStream } from 'stream/web';
5
+ import type { AdaptiveInterruptionDetector } from './AdaptiveInterruptionDetector.js';
6
+ import { type InterruptionEvent } from './interruption.js';
7
+ export interface AgentSpeechStarted {
8
+ type: 'agent-speech-started';
9
+ }
10
+ export interface AgentSpeechEnded {
11
+ type: 'agent-speech-ended';
12
+ }
13
+ export interface OverlapSpeechStarted {
14
+ type: 'overlap-speech-started';
15
+ speechDuration: number;
16
+ userSpeakingSpan: Span;
17
+ }
18
+ export interface OverlapSpeechEnded {
19
+ type: 'overlap-speech-ended';
20
+ }
21
+ export interface Flush {
22
+ type: 'flush';
23
+ }
24
+ export type InterruptionSentinel = AgentSpeechStarted | AgentSpeechEnded | OverlapSpeechStarted | OverlapSpeechEnded | Flush;
25
+ export declare class InterruptionStreamSentinel {
26
+ static speechStarted(): AgentSpeechStarted;
27
+ static speechEnded(): AgentSpeechEnded;
28
+ static overlapSpeechStarted(speechDuration: number, userSpeakingSpan: Span): OverlapSpeechStarted;
29
+ static overlapSpeechEnded(): OverlapSpeechEnded;
30
+ static flush(): Flush;
31
+ }
32
+ export interface ApiConnectOptions {
33
+ maxRetries: number;
34
+ retryInterval: number;
35
+ timeout: number;
36
+ }
37
+ export declare class InterruptionStreamBase {
38
+ private inputStream;
39
+ private eventStream;
40
+ private resampler?;
41
+ private userSpeakingSpan;
42
+ private overlapSpeechStartedAt;
43
+ private options;
44
+ private apiOptions;
45
+ private model;
46
+ constructor(model: AdaptiveInterruptionDetector, apiOptions: Partial<ApiConnectOptions>);
47
+ private setupTransform;
48
+ private ensureInputNotEnded;
49
+ private ensureStreamsNotEnded;
50
+ private getResamplerFor;
51
+ get stream(): ReadableStream<InterruptionEvent>;
52
+ pushFrame(frame: InterruptionSentinel | AudioFrame): Promise<void>;
53
+ flush(): Promise<void>;
54
+ endInput(): Promise<void>;
55
+ close(): Promise<void>;
56
+ }
57
+ //# sourceMappingURL=InterruptionStream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InterruptionStream.d.ts","sourceRoot":"","sources":["../../../src/inference/interruption/InterruptionStream.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,UAAU,EAAkB,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,KAAK,cAAc,EAAmC,MAAM,YAAY,CAAC;AAIlF,OAAO,KAAK,EACV,4BAA4B,EAE7B,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,oBAAoB,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,sBAAsB,CAAC;CAC9B;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,MAAM,oBAAoB,GAC5B,kBAAkB,GAClB,gBAAgB,GAChB,oBAAoB,GACpB,kBAAkB,GAClB,KAAK,CAAC;AAEV,qBAAa,0BAA0B;IACrC,MAAM,CAAC,aAAa,IAAI,kBAAkB;IAI1C,MAAM,CAAC,WAAW,IAAI,gBAAgB;IAItC,MAAM,CAAC,oBAAoB,CACzB,cAAc,EAAE,MAAM,EACtB,gBAAgB,EAAE,IAAI,GACrB,oBAAoB;IAIvB,MAAM,CAAC,kBAAkB,IAAI,kBAAkB;IAI/C,MAAM,CAAC,KAAK,IAAI,KAAK;CAGtB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAaD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,WAAW,CAA+E;IAElG,OAAO,CAAC,WAAW,CAA+D;IAElF,OAAO,CAAC,SAAS,CAAC,CAAiB;IAEnC,OAAO,CAAC,gBAAgB,CAAmB;IAE3C,OAAO,CAAC,sBAAsB,CAAqB;IAEnD,OAAO,CAAC,OAAO,CAAsB;IAErC,OAAO,CAAC,UAAU,CAAoB;IAEtC,OAAO,CAAC,KAAK,CAA+B;gBAEhC,KAAK,EAAE,4BAA4B,EAAE,UAAU,EAAE,OAAO,CAAC,iBAAiB,CAAC;IAevF,OAAO,CAAC,cAAc;IA2KtB,OAAO,CAAC,mBAAmB;IAM3B,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,eAAe;IAOvB,IAAI,MAAM,IAAI,cAAc,CAAC,iBAAiB,CAAC,CAE9C;IAEK,SAAS,CAAC,KAAK,EAAE,oBAAoB,GAAG,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBlE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}