Now that we can stream transcripts from a microphone, we're going to surface the VAD and end-of-turn events the engine already tracks.
transcribeStream returns a duplex session. Audio goes in via session.write(chunk), and the session yields a discriminated union of events: text chunks, voice-activity state, and turn boundaries. A real-time voice assistant builds on top of those three event types.
Setting emitVadEvents: true and an endOfTurnSilenceMs of 800 surfaces VAD and end-of-turn events. The session open looks like this:
const session = await transcribeStream({
modelId,
emitVadEvents: true,
endOfTurnSilenceMs: 800,
});
void (async () => {
for await (const chunk of audioStream()) {
session.write(chunk);
}
})();Three event types on the same duplex stream, one for await + switch is the canonical consume pattern:
for await (const event of session) {
switch (event.type) {
case "text":
console.log(`> ${event.text.trim()}`);
break;
case "vad":
console.log(`▸ [vad] speaking=${event.speaking} probability=${event.probability.toFixed(2)}`);
break;
case "endOfTurn":
console.log(`▸ [endOfTurn] silence ${event.silenceDurationMs}ms\n`);
break;
}
}The vad event fires while the speaker is talking. The endOfTurn event fires after the speaker pauses for endOfTurnSilenceMs milliseconds. That silence window is the conversation-equivalent of "they're done; now I can answer."
Note:
endOfTurnmeasures silence from the VAD, not from a parakeet EOU token. Pair the Whisper model withVAD_SILERO_5_1_2inmodelConfig.vadModelSrcso the silence window is measured accurately.
transcribeStream({ modelId, emitVadEvents: true, endOfTurnSilenceMs: 800 }), pipe the mic stream's Float32Array frames into session.write(chunk) in a background task, then iterate for await (const event of session).event.type. Console.log text events as transcript lines, vad events as VAD ticks, and endOfTurn events as turn boundaries.$ Run your code to see results
$