vocal-stack 1.0.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.
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Configuration for flow control
3
+ */
4
+ interface FlowConfig {
5
+ /**
6
+ * Stall threshold in milliseconds
7
+ * @default 700
8
+ */
9
+ readonly stallThresholdMs?: number;
10
+ /**
11
+ * Filler phrases to inject
12
+ * @default ['um', 'let me think', 'hmm']
13
+ */
14
+ readonly fillerPhrases?: readonly string[];
15
+ /**
16
+ * Whether to enable filler injection
17
+ * @default true
18
+ */
19
+ readonly enableFillers?: boolean;
20
+ /**
21
+ * Maximum number of fillers to inject per response
22
+ * @default 3
23
+ */
24
+ readonly maxFillersPerResponse?: number;
25
+ /**
26
+ * Callback when filler is injected
27
+ */
28
+ readonly onFillerInjected?: (filler: string) => void;
29
+ /**
30
+ * Callback when stall is detected
31
+ */
32
+ readonly onStallDetected?: (durationMs: number) => void;
33
+ /**
34
+ * Callback when first chunk is emitted
35
+ */
36
+ readonly onFirstChunk?: () => void;
37
+ }
38
+ /**
39
+ * Conversation states
40
+ */
41
+ declare enum ConversationState {
42
+ IDLE = "idle",
43
+ WAITING = "waiting",
44
+ SPEAKING = "speaking",
45
+ INTERRUPTED = "interrupted"
46
+ }
47
+ /**
48
+ * Statistics tracked by flow controller
49
+ */
50
+ interface FlowStats {
51
+ readonly fillersInjected: number;
52
+ readonly stallsDetected: number;
53
+ readonly chunksProcessed: number;
54
+ readonly firstChunkTime: number | null;
55
+ readonly totalDurationMs: number;
56
+ }
57
+ /**
58
+ * Flow events for low-level API
59
+ */
60
+ type FlowEvent = {
61
+ type: 'stall-detected';
62
+ durationMs: number;
63
+ } | {
64
+ type: 'filler-injected';
65
+ filler: string;
66
+ } | {
67
+ type: 'first-chunk';
68
+ chunk: string;
69
+ } | {
70
+ type: 'state-change';
71
+ from: ConversationState;
72
+ to: ConversationState;
73
+ } | {
74
+ type: 'interrupted';
75
+ } | {
76
+ type: 'chunk-processed';
77
+ chunk: string;
78
+ } | {
79
+ type: 'completed';
80
+ stats: FlowStats;
81
+ };
82
+ /**
83
+ * Event listener for flow events
84
+ */
85
+ type FlowEventListener = (event: FlowEvent) => void;
86
+ /**
87
+ * Configuration for low-level FlowManager
88
+ */
89
+ interface FlowManagerConfig {
90
+ /**
91
+ * Stall threshold in milliseconds
92
+ * @default 700
93
+ */
94
+ readonly stallThresholdMs?: number;
95
+ /**
96
+ * Filler phrases to inject
97
+ * @default ['um', 'let me think', 'hmm']
98
+ */
99
+ readonly fillerPhrases?: readonly string[];
100
+ /**
101
+ * Whether to enable filler injection
102
+ * @default true
103
+ */
104
+ readonly enableFillers?: boolean;
105
+ /**
106
+ * Maximum number of fillers to inject per response
107
+ * @default 3
108
+ */
109
+ readonly maxFillersPerResponse?: number;
110
+ /**
111
+ * Buffer size for barge-in scenarios
112
+ * @default 10
113
+ */
114
+ readonly bufferSize?: number;
115
+ }
116
+
117
+ /**
118
+ * High-level stream wrapper for flow control
119
+ */
120
+ declare class FlowController {
121
+ private readonly config;
122
+ private readonly stateMachine;
123
+ private readonly stallDetector;
124
+ private readonly fillerInjector;
125
+ private readonly bufferManager;
126
+ private firstChunkEmitted;
127
+ private stats;
128
+ private startTime;
129
+ constructor(config?: FlowConfig);
130
+ /**
131
+ * Wrap an async iterable with flow control
132
+ */
133
+ wrap(input: AsyncIterable<string>): AsyncIterable<string>;
134
+ /**
135
+ * Interrupt the current flow (for barge-in)
136
+ */
137
+ interrupt(): void;
138
+ /**
139
+ * Get current conversation state
140
+ */
141
+ getState(): ConversationState;
142
+ /**
143
+ * Get flow statistics
144
+ */
145
+ getStats(): FlowStats;
146
+ /**
147
+ * Get buffered chunks (for advanced barge-in scenarios)
148
+ */
149
+ getBufferedChunks(): readonly string[];
150
+ private handleStall;
151
+ private reset;
152
+ }
153
+ /**
154
+ * Convenience function to create and use flow controller
155
+ */
156
+ declare function withFlowControl(input: AsyncIterable<string>, config?: FlowConfig): AsyncIterable<string>;
157
+
158
+ /**
159
+ * Low-level event-based flow manager
160
+ */
161
+ declare class FlowManager {
162
+ private readonly config;
163
+ private readonly stateMachine;
164
+ private readonly stallDetector;
165
+ private readonly fillerInjector;
166
+ private readonly bufferManager;
167
+ private readonly listeners;
168
+ private firstChunkEmitted;
169
+ private stats;
170
+ private startTime;
171
+ private stateChangeUnsubscribe;
172
+ constructor(config?: FlowManagerConfig);
173
+ /**
174
+ * Add event listener
175
+ */
176
+ on(listener: FlowEventListener): () => void;
177
+ /**
178
+ * Start flow tracking
179
+ */
180
+ start(): void;
181
+ /**
182
+ * Process a chunk from the stream
183
+ */
184
+ processChunk(chunk: string): void;
185
+ /**
186
+ * Complete the flow
187
+ */
188
+ complete(): void;
189
+ /**
190
+ * Interrupt the flow (for barge-in)
191
+ */
192
+ interrupt(): void;
193
+ /**
194
+ * Get current conversation state
195
+ */
196
+ getState(): ConversationState;
197
+ /**
198
+ * Get flow statistics
199
+ */
200
+ getStats(): FlowStats;
201
+ /**
202
+ * Get buffered chunks
203
+ */
204
+ getBufferedChunks(): readonly string[];
205
+ private handleStall;
206
+ private emit;
207
+ private reset;
208
+ }
209
+
210
+ /**
211
+ * Conversation state machine
212
+ */
213
+ declare class ConversationStateMachine {
214
+ private currentState;
215
+ private readonly listeners;
216
+ /**
217
+ * Get current state
218
+ */
219
+ getState(): ConversationState;
220
+ /**
221
+ * Attempt to transition to new state
222
+ */
223
+ transition(to: ConversationState): boolean;
224
+ /**
225
+ * Add state change listener
226
+ */
227
+ onStateChange(listener: (from: ConversationState, to: ConversationState) => void): () => void;
228
+ /**
229
+ * Reset to IDLE
230
+ */
231
+ reset(): void;
232
+ }
233
+
234
+ /**
235
+ * Default stall threshold in milliseconds
236
+ * Based on human perception of silence: 500-1000ms feels like a pause
237
+ * Most LLM APIs stream chunks every 50-200ms when active
238
+ */
239
+ declare const DEFAULT_STALL_THRESHOLD_MS = 700;
240
+ /**
241
+ * Default filler phrases to inject during stalls
242
+ */
243
+ declare const DEFAULT_FILLER_PHRASES: string[];
244
+ /**
245
+ * Default maximum fillers per response
246
+ * Prevents over-use of filler words
247
+ */
248
+ declare const DEFAULT_MAX_FILLERS_PER_RESPONSE = 3;
249
+
250
+ /**
251
+ * Detects stream stalls based on timing
252
+ */
253
+ declare class StallDetector {
254
+ private lastChunkTime;
255
+ private stallTimer;
256
+ private readonly thresholdMs;
257
+ private readonly onStall;
258
+ constructor(thresholdMs: number, onStall: (durationMs: number) => void);
259
+ /**
260
+ * Notify detector that a chunk was received
261
+ */
262
+ notifyChunk(): void;
263
+ /**
264
+ * Start monitoring for stalls
265
+ */
266
+ start(): void;
267
+ /**
268
+ * Stop monitoring
269
+ */
270
+ stop(): void;
271
+ private scheduleStallCheck;
272
+ private clearTimer;
273
+ }
274
+
275
+ /**
276
+ * Manages filler phrase injection
277
+ */
278
+ declare class FillerInjector {
279
+ private readonly phrases;
280
+ private readonly maxFillers;
281
+ private fillersUsed;
282
+ private lastFillerIndex;
283
+ constructor(phrases: readonly string[], maxFillers: number);
284
+ /**
285
+ * Get next filler phrase (returns null if limit reached)
286
+ */
287
+ getFiller(): string | null;
288
+ /**
289
+ * Reset filler state
290
+ */
291
+ reset(): void;
292
+ /**
293
+ * Check if more fillers can be injected
294
+ */
295
+ canInjectMore(): boolean;
296
+ /**
297
+ * Get count of fillers used
298
+ */
299
+ getUsedCount(): number;
300
+ }
301
+
302
+ /**
303
+ * Buffer manager for barge-in scenarios
304
+ */
305
+ declare class BufferManager {
306
+ private buffer;
307
+ private readonly maxSize;
308
+ private head;
309
+ private size;
310
+ constructor(maxSize?: number);
311
+ /**
312
+ * Add chunk to buffer
313
+ */
314
+ add(chunk: string): void;
315
+ /**
316
+ * Get all buffered chunks in order
317
+ */
318
+ getAll(): readonly string[];
319
+ /**
320
+ * Clear all buffered chunks
321
+ */
322
+ clear(): void;
323
+ /**
324
+ * Get current buffer size
325
+ */
326
+ getSize(): number;
327
+ /**
328
+ * Check if buffer is empty
329
+ */
330
+ isEmpty(): boolean;
331
+ /**
332
+ * Check if buffer is full
333
+ */
334
+ isFull(): boolean;
335
+ }
336
+
337
+ export { BufferManager, ConversationState, ConversationStateMachine, DEFAULT_FILLER_PHRASES, DEFAULT_MAX_FILLERS_PER_RESPONSE, DEFAULT_STALL_THRESHOLD_MS, FillerInjector, type FlowConfig, FlowController, type FlowEvent, type FlowEventListener, FlowManager, type FlowManagerConfig, type FlowStats, StallDetector, withFlowControl };
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Configuration for flow control
3
+ */
4
+ interface FlowConfig {
5
+ /**
6
+ * Stall threshold in milliseconds
7
+ * @default 700
8
+ */
9
+ readonly stallThresholdMs?: number;
10
+ /**
11
+ * Filler phrases to inject
12
+ * @default ['um', 'let me think', 'hmm']
13
+ */
14
+ readonly fillerPhrases?: readonly string[];
15
+ /**
16
+ * Whether to enable filler injection
17
+ * @default true
18
+ */
19
+ readonly enableFillers?: boolean;
20
+ /**
21
+ * Maximum number of fillers to inject per response
22
+ * @default 3
23
+ */
24
+ readonly maxFillersPerResponse?: number;
25
+ /**
26
+ * Callback when filler is injected
27
+ */
28
+ readonly onFillerInjected?: (filler: string) => void;
29
+ /**
30
+ * Callback when stall is detected
31
+ */
32
+ readonly onStallDetected?: (durationMs: number) => void;
33
+ /**
34
+ * Callback when first chunk is emitted
35
+ */
36
+ readonly onFirstChunk?: () => void;
37
+ }
38
+ /**
39
+ * Conversation states
40
+ */
41
+ declare enum ConversationState {
42
+ IDLE = "idle",
43
+ WAITING = "waiting",
44
+ SPEAKING = "speaking",
45
+ INTERRUPTED = "interrupted"
46
+ }
47
+ /**
48
+ * Statistics tracked by flow controller
49
+ */
50
+ interface FlowStats {
51
+ readonly fillersInjected: number;
52
+ readonly stallsDetected: number;
53
+ readonly chunksProcessed: number;
54
+ readonly firstChunkTime: number | null;
55
+ readonly totalDurationMs: number;
56
+ }
57
+ /**
58
+ * Flow events for low-level API
59
+ */
60
+ type FlowEvent = {
61
+ type: 'stall-detected';
62
+ durationMs: number;
63
+ } | {
64
+ type: 'filler-injected';
65
+ filler: string;
66
+ } | {
67
+ type: 'first-chunk';
68
+ chunk: string;
69
+ } | {
70
+ type: 'state-change';
71
+ from: ConversationState;
72
+ to: ConversationState;
73
+ } | {
74
+ type: 'interrupted';
75
+ } | {
76
+ type: 'chunk-processed';
77
+ chunk: string;
78
+ } | {
79
+ type: 'completed';
80
+ stats: FlowStats;
81
+ };
82
+ /**
83
+ * Event listener for flow events
84
+ */
85
+ type FlowEventListener = (event: FlowEvent) => void;
86
+ /**
87
+ * Configuration for low-level FlowManager
88
+ */
89
+ interface FlowManagerConfig {
90
+ /**
91
+ * Stall threshold in milliseconds
92
+ * @default 700
93
+ */
94
+ readonly stallThresholdMs?: number;
95
+ /**
96
+ * Filler phrases to inject
97
+ * @default ['um', 'let me think', 'hmm']
98
+ */
99
+ readonly fillerPhrases?: readonly string[];
100
+ /**
101
+ * Whether to enable filler injection
102
+ * @default true
103
+ */
104
+ readonly enableFillers?: boolean;
105
+ /**
106
+ * Maximum number of fillers to inject per response
107
+ * @default 3
108
+ */
109
+ readonly maxFillersPerResponse?: number;
110
+ /**
111
+ * Buffer size for barge-in scenarios
112
+ * @default 10
113
+ */
114
+ readonly bufferSize?: number;
115
+ }
116
+
117
+ /**
118
+ * High-level stream wrapper for flow control
119
+ */
120
+ declare class FlowController {
121
+ private readonly config;
122
+ private readonly stateMachine;
123
+ private readonly stallDetector;
124
+ private readonly fillerInjector;
125
+ private readonly bufferManager;
126
+ private firstChunkEmitted;
127
+ private stats;
128
+ private startTime;
129
+ constructor(config?: FlowConfig);
130
+ /**
131
+ * Wrap an async iterable with flow control
132
+ */
133
+ wrap(input: AsyncIterable<string>): AsyncIterable<string>;
134
+ /**
135
+ * Interrupt the current flow (for barge-in)
136
+ */
137
+ interrupt(): void;
138
+ /**
139
+ * Get current conversation state
140
+ */
141
+ getState(): ConversationState;
142
+ /**
143
+ * Get flow statistics
144
+ */
145
+ getStats(): FlowStats;
146
+ /**
147
+ * Get buffered chunks (for advanced barge-in scenarios)
148
+ */
149
+ getBufferedChunks(): readonly string[];
150
+ private handleStall;
151
+ private reset;
152
+ }
153
+ /**
154
+ * Convenience function to create and use flow controller
155
+ */
156
+ declare function withFlowControl(input: AsyncIterable<string>, config?: FlowConfig): AsyncIterable<string>;
157
+
158
+ /**
159
+ * Low-level event-based flow manager
160
+ */
161
+ declare class FlowManager {
162
+ private readonly config;
163
+ private readonly stateMachine;
164
+ private readonly stallDetector;
165
+ private readonly fillerInjector;
166
+ private readonly bufferManager;
167
+ private readonly listeners;
168
+ private firstChunkEmitted;
169
+ private stats;
170
+ private startTime;
171
+ private stateChangeUnsubscribe;
172
+ constructor(config?: FlowManagerConfig);
173
+ /**
174
+ * Add event listener
175
+ */
176
+ on(listener: FlowEventListener): () => void;
177
+ /**
178
+ * Start flow tracking
179
+ */
180
+ start(): void;
181
+ /**
182
+ * Process a chunk from the stream
183
+ */
184
+ processChunk(chunk: string): void;
185
+ /**
186
+ * Complete the flow
187
+ */
188
+ complete(): void;
189
+ /**
190
+ * Interrupt the flow (for barge-in)
191
+ */
192
+ interrupt(): void;
193
+ /**
194
+ * Get current conversation state
195
+ */
196
+ getState(): ConversationState;
197
+ /**
198
+ * Get flow statistics
199
+ */
200
+ getStats(): FlowStats;
201
+ /**
202
+ * Get buffered chunks
203
+ */
204
+ getBufferedChunks(): readonly string[];
205
+ private handleStall;
206
+ private emit;
207
+ private reset;
208
+ }
209
+
210
+ /**
211
+ * Conversation state machine
212
+ */
213
+ declare class ConversationStateMachine {
214
+ private currentState;
215
+ private readonly listeners;
216
+ /**
217
+ * Get current state
218
+ */
219
+ getState(): ConversationState;
220
+ /**
221
+ * Attempt to transition to new state
222
+ */
223
+ transition(to: ConversationState): boolean;
224
+ /**
225
+ * Add state change listener
226
+ */
227
+ onStateChange(listener: (from: ConversationState, to: ConversationState) => void): () => void;
228
+ /**
229
+ * Reset to IDLE
230
+ */
231
+ reset(): void;
232
+ }
233
+
234
+ /**
235
+ * Default stall threshold in milliseconds
236
+ * Based on human perception of silence: 500-1000ms feels like a pause
237
+ * Most LLM APIs stream chunks every 50-200ms when active
238
+ */
239
+ declare const DEFAULT_STALL_THRESHOLD_MS = 700;
240
+ /**
241
+ * Default filler phrases to inject during stalls
242
+ */
243
+ declare const DEFAULT_FILLER_PHRASES: string[];
244
+ /**
245
+ * Default maximum fillers per response
246
+ * Prevents over-use of filler words
247
+ */
248
+ declare const DEFAULT_MAX_FILLERS_PER_RESPONSE = 3;
249
+
250
+ /**
251
+ * Detects stream stalls based on timing
252
+ */
253
+ declare class StallDetector {
254
+ private lastChunkTime;
255
+ private stallTimer;
256
+ private readonly thresholdMs;
257
+ private readonly onStall;
258
+ constructor(thresholdMs: number, onStall: (durationMs: number) => void);
259
+ /**
260
+ * Notify detector that a chunk was received
261
+ */
262
+ notifyChunk(): void;
263
+ /**
264
+ * Start monitoring for stalls
265
+ */
266
+ start(): void;
267
+ /**
268
+ * Stop monitoring
269
+ */
270
+ stop(): void;
271
+ private scheduleStallCheck;
272
+ private clearTimer;
273
+ }
274
+
275
+ /**
276
+ * Manages filler phrase injection
277
+ */
278
+ declare class FillerInjector {
279
+ private readonly phrases;
280
+ private readonly maxFillers;
281
+ private fillersUsed;
282
+ private lastFillerIndex;
283
+ constructor(phrases: readonly string[], maxFillers: number);
284
+ /**
285
+ * Get next filler phrase (returns null if limit reached)
286
+ */
287
+ getFiller(): string | null;
288
+ /**
289
+ * Reset filler state
290
+ */
291
+ reset(): void;
292
+ /**
293
+ * Check if more fillers can be injected
294
+ */
295
+ canInjectMore(): boolean;
296
+ /**
297
+ * Get count of fillers used
298
+ */
299
+ getUsedCount(): number;
300
+ }
301
+
302
+ /**
303
+ * Buffer manager for barge-in scenarios
304
+ */
305
+ declare class BufferManager {
306
+ private buffer;
307
+ private readonly maxSize;
308
+ private head;
309
+ private size;
310
+ constructor(maxSize?: number);
311
+ /**
312
+ * Add chunk to buffer
313
+ */
314
+ add(chunk: string): void;
315
+ /**
316
+ * Get all buffered chunks in order
317
+ */
318
+ getAll(): readonly string[];
319
+ /**
320
+ * Clear all buffered chunks
321
+ */
322
+ clear(): void;
323
+ /**
324
+ * Get current buffer size
325
+ */
326
+ getSize(): number;
327
+ /**
328
+ * Check if buffer is empty
329
+ */
330
+ isEmpty(): boolean;
331
+ /**
332
+ * Check if buffer is full
333
+ */
334
+ isFull(): boolean;
335
+ }
336
+
337
+ export { BufferManager, ConversationState, ConversationStateMachine, DEFAULT_FILLER_PHRASES, DEFAULT_MAX_FILLERS_PER_RESPONSE, DEFAULT_STALL_THRESHOLD_MS, FillerInjector, type FlowConfig, FlowController, type FlowEvent, type FlowEventListener, FlowManager, type FlowManagerConfig, type FlowStats, StallDetector, withFlowControl };