@charivo/stt
v0.11.1
Published
STT manager and browser adapters for Charivo
Maintainers
Readme
@charivo/stt
Stateful STT manager and recording helper for Charivo.
Install
pnpm add @charivo/sttRequires Node.js 22 or newer, as declared in this package's engines.
Usage
import { createSTTManager } from "@charivo/stt";
import { createRemoteSTTTranscriber } from "@charivo/stt/remote";
const sttManager = createSTTManager(
createRemoteSTTTranscriber({ apiEndpoint: "/api/stt" }),
);
await sttManager.start({ language: "en" });
const text = await sttManager.stop();Exports
createSTTManager(transcriber)isWebSTTSupported()(from@charivo/stt/web) — SSR-safe check for Web Speech recognition support@charivo/stt/openai:createOpenAISTTTranscriber(config)(browser transcriber, dev/testing only) and, for server-side use,createOpenAISTTProvider(config),OpenAISTTProvider,type OpenAISTTConfig@charivo/stt/gemini:createGeminiSTTTranscriber(config)(browser transcriber, dev/testing only) and, for server-side use,createGeminiSTTProvider(config),GeminiSTTProvider,type GeminiSTTConfig. The recording is posted inline (base64) to Gemini'smodels/{model}:generateContentoverfetch, with a default model ofgemini-3.5-transcribe.languageis optional and only a soft hint — the model transcribes what it hears even when the hint is wrong.timeoutMsdefaults to 30s and also covers reading the response body. Inline requests are capped at 20MB, and there is no streaming:gemini-3.5-transcribe-liveis WebSocket-only —generateContentrejects it with a 400 — and is served by@charivo/stt/gemini-livebelow. Gemini's free-tier rate limit surfaces as a 429 wrapped in a provider error.@charivo/stt/openai-realtime:createOpenAIRealtimeSTTTranscriber({ bootstrap })(live WebRTC streaming transcriber,gpt-realtime-whisper) — the app injectsbootstrap(request) => Promise<{ answerSdp }>, owns the credentials, and mints thetype: "transcription"session withturn_detection: null; no key-bearing helper is shipped@charivo/stt/gemini-live:createGeminiLiveSTTTranscriber({ bootstrap })(live WebSocket streaming transcriber over the Gemini Live API,gemini-3.5-transcribe-live) — the app injectsbootstrap(request) => Promise<{ url, token }>, owns the credentials, and mints the single-use ephemeral token whose setup pins the model, theTEXTresponse modality, and manual VAD. No key-bearing helper is shipped, but unlike the OpenAI path's SDP answer that token is itself a credential: the browser holds it for the life of one recording.urlmust be the Live API'sBidiGenerateContentConstrainedwebsocket endpoint (the ephemeral-token one), with schemews:orwss:(the transcriber rejects any other scheme) and no fragment (the nativeWebSocketconstructor rejects one); the transcriber parsesurland addsaccess_tokenas a query parameter, so an existing query string on it survives. Pinning manual VAD in the token is a requirement rather than a detail: itsbidiGenerateContentSetupreplaces the setup frame the transcriber sends rather than merging with it, so a bootstrap that leaves manual VAD out silently loses the recording — server VAD cuts the audio at every pause, each transcription replaces the running snapshot instead of extending it, andstop()then returns the last segment alone or times out. Eachstt:partialis a whole-recording snapshot that may be revised, not a delta, so a UI must replace its draft rather than append to it
Event Bridge
STTManager accepts an emit-only event bridge through setEventEmitter(...).
It emits STT lifecycle and error events back into core, and does not subscribe
through the shared event bus.
When connected, the manager emits:
stt:startstt:partial(streaming transcribers only —@charivo/stt/openai-realtimeand@charivo/stt/gemini-live)stt:stopstt:error
For either streaming transcriber, a mid-session failure does not push an event
on its own — it surfaces the next time the app calls stop(), which rejects and
emits stt:error (never a successful stt:stop).
