@speechrouter/sdk 0.1.0 → 0.3.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 (2) hide show
  1. package/README.md +254 -4
  2. package/package.json +6 -4
package/README.md CHANGED
@@ -1,10 +1,260 @@
1
- # @speechrouter/sdk
1
+ # speechrouter
2
2
 
3
- Official scoped alias of [`speechrouter`](https://www.npmjs.com/package/speechrouter)
4
- one API for every speech model, [speechrouter.ai](https://speechrouter.ai).
3
+ One API for every speech model — [speechrouter.ai](https://speechrouter.ai)
4
+
5
+ Streaming speech-to-text over WebSocket with **mid-stream provider failover**,
6
+ plus batch transcription. One key, one schema, 30+ models across 12 providers —
7
+ switch vendors by editing a string.
8
+
9
+ - **Works everywhere** — browsers, Node ≥ 18, React Native, edge runtimes
10
+ - **Zero runtime dependencies**
11
+ - **Fully typed** — every wire event and error code ships as a TypeScript type
5
12
 
6
13
  ```sh
7
14
  npm install speechrouter
8
15
  ```
9
16
 
10
- Both names ship the same client; `speechrouter` is canonical.
17
+ ## Quickstart live transcription
18
+
19
+ ```ts
20
+ import { SpeechRouter } from "speechrouter";
21
+
22
+ const sr = new SpeechRouter({ apiKey: "sk_sr_..." });
23
+
24
+ const stream = sr.listen({
25
+ model: "deepgram/nova-3",
26
+ fallbacks: ["soniox/stt-rt-v5"], // primary dies mid-stream? we switch, you keep captioning
27
+ });
28
+
29
+ stream.on("open", (s) => console.log("session", s.session_id));
30
+ stream.on("transcript", (t) => {
31
+ if (t.is_final) console.log("✔", t.text);
32
+ else console.log("…", t.text);
33
+ });
34
+ stream.on("provider_switched", (s) =>
35
+ console.log(`failover: ${s.from} → ${s.to}, resumed at ${s.resumed_at}s`),
36
+ );
37
+ stream.on("error", (e) => console.error(e.code, e.message));
38
+
39
+ stream.sendAudio(pcmChunk); // 16-bit linear PCM, 16 kHz mono by default
40
+
41
+ const done = await stream.stop(); // finalize → transcript tail → usage
42
+ console.log(done.usage); // { audio_seconds: 12.4, model: "deepgram/nova-3" }
43
+ ```
44
+
45
+ Prefer async iteration? Every session is an `AsyncIterable` of wire events:
46
+
47
+ ```ts
48
+ for await (const event of stream) {
49
+ if (event.type === "transcript" && event.is_final) console.log(event.text);
50
+ if (event.type === "provider_switched") console.log("failover!");
51
+ }
52
+ ```
53
+
54
+ ## Microphone (browser)
55
+
56
+ `speechrouter/mic` captures the default mic, resamples whatever rate the
57
+ browser gives you (96 kHz interfaces included) down to 16 kHz PCM, and pumps
58
+ it into the stream:
59
+
60
+ ```ts
61
+ import { SpeechRouter } from "speechrouter";
62
+ import { openMicrophone } from "speechrouter/mic";
63
+
64
+ const stream = new SpeechRouter({ apiKey }).listen({ model: "deepgram/flux-general-en" });
65
+ const mic = await openMicrophone(stream, {
66
+ onLevel: (rms) => (meter.style.width = `${rms * 300}px`),
67
+ });
68
+
69
+ // later:
70
+ mic.stop();
71
+ const { usage } = await stream.stop();
72
+ ```
73
+
74
+ It's a separate entry point so React Native and server bundles never touch
75
+ browser APIs.
76
+
77
+ ## React
78
+
79
+ ```tsx
80
+ function Captions({ apiKey }: { apiKey: string }) {
81
+ const [text, setText] = useState("");
82
+ const [interim, setInterim] = useState("");
83
+
84
+ useEffect(() => {
85
+ const stream = new SpeechRouter({ apiKey }).listen({ model: "deepgram/nova-3" });
86
+ const offT = stream.on("transcript", (t) =>
87
+ t.is_final ? (setText((p) => p + " " + t.text), setInterim("")) : setInterim(t.text),
88
+ );
89
+ let mic: { stop(): void } | undefined;
90
+ void import("speechrouter/mic").then(async (m) => (mic = await m.openMicrophone(stream)));
91
+ return () => {
92
+ offT();
93
+ mic?.stop();
94
+ stream.close();
95
+ };
96
+ }, [apiKey]);
97
+
98
+ return <p>{text} <i>{interim}</i></p>;
99
+ }
100
+ ```
101
+
102
+ ## React Native
103
+
104
+ The core client is RN-safe (global WebSocket, no Node or DOM APIs). Capture
105
+ PCM with a native module and feed the stream:
106
+
107
+ ```ts
108
+ import LiveAudioStream from "react-native-live-audio-stream";
109
+ import { Buffer } from "buffer";
110
+
111
+ LiveAudioStream.init({ sampleRate: 16000, channels: 1, bitsPerSample: 16, bufferSize: 4096 });
112
+ const stream = sr.listen({ model: "deepgram/nova-3" });
113
+ LiveAudioStream.on("data", (b64) => stream.sendAudio(Buffer.from(b64, "base64")));
114
+ LiveAudioStream.start();
115
+ ```
116
+
117
+ For batch uploads, pass an RN file descriptor: `{ uri, name, type }`.
118
+
119
+ ## Node
120
+
121
+ ```ts
122
+ import { createReadStream } from "node:fs";
123
+
124
+ // stream a file as if it were live audio
125
+ const stream = sr.listen({ model: "soniox/stt-rt-v5" });
126
+ for await (const chunk of createReadStream("call.raw", { highWaterMark: 8000 })) {
127
+ stream.sendAudio(chunk);
128
+ await new Promise((r) => setTimeout(r, 40)); // pace ~real-time when required
129
+ }
130
+ const { usage } = await stream.stop();
131
+ ```
132
+
133
+ Node 22+ uses the built-in WebSocket. Node 18–21: `npm install ws`
134
+ (optional peer dependency, picked up automatically).
135
+
136
+ ## Batch transcription
137
+
138
+ ```ts
139
+ // simplest: { text }
140
+ const { text } = await sr.transcribe({ model: "cartesia/ink-whisper", file });
141
+
142
+ // word timings, language, duration
143
+ const verbose = await sr.transcribe({ model: "deepgram/nova-3", file, responseFormat: "verbose_json" });
144
+
145
+ // subtitles straight out
146
+ const srt = await sr.transcribe({ model: "groq/whisper-large-v3", file, responseFormat: "srt" });
147
+
148
+ // let the gateway fetch the audio itself
149
+ await sr.transcribe({ model: "deepgram/nova-3", url: "https://example.com/call.mp3" });
150
+ ```
151
+
152
+ | `responseFormat` | returns |
153
+ | --- | --- |
154
+ | `json` (default) | `{ text }` |
155
+ | `verbose_json` | text + words with timings + language + duration |
156
+ | `srt` / `vtt` | subtitle file as a string — synthesized from word timings even when the vendor won't |
157
+ | `text` | plain string |
158
+
159
+ `file` accepts a `File`/`Blob`, `Uint8Array`/`ArrayBuffer`, or a React Native
160
+ descriptor `{ uri, name, type }`. Files up to 250 MB.
161
+
162
+ ## Models
163
+
164
+ ```ts
165
+ const models = await sr.listModels();
166
+ // [{ slug: "deepgram/nova-3", modes: ["streaming","batch"], pricing: {...}, capabilities: {...} }, ...]
167
+ ```
168
+
169
+ Live catalog with pricing: [speechrouter.ai/models](https://speechrouter.ai/models)
170
+
171
+ ## `listen()` options
172
+
173
+ | option | default | |
174
+ | --- | --- | --- |
175
+ | `model` | — | model slug, e.g. `"deepgram/nova-3"` |
176
+ | `fallbacks` | `[]` | ordered failover lane; audio is replayed into the takeover so no words are lost |
177
+ | `encoding` | `"linear16"` | PCM encoding of the audio you send |
178
+ | `sampleRate` | `16000` | sample rate of the audio you send |
179
+ | `channels` | `1` | channel count |
180
+ | `language` | auto | BCP-47 hint |
181
+ | `interimResults` | `true` | emit non-final hypotheses |
182
+ | `diarization` | `false` | speaker labels on words |
183
+ | `keyterms` | `[]` | bias recognition toward these terms |
184
+ | `includeRaw` | `false` | attach the untouched provider payload to every transcript |
185
+ | `providerParams` | `{}` | escape hatch: raw params forwarded to the provider |
186
+ | `connectTimeoutMs` | `10000` | dial timeout |
187
+ | `keepAlive` | `true` (8s) | keep the session alive through silences; `false` to disable — an open session bills wall-clock time on session-billed providers |
188
+
189
+ ## Events
190
+
191
+ | event | payload | when |
192
+ | --- | --- | --- |
193
+ | `open` | `session_id`, `model` | gateway accepted the session |
194
+ | `transcript` | `is_final`, `text`, `words[]` (`w`,`start`,`end`,`conf`,`speaker`), `provider_raw?` | hypothesis or final |
195
+ | `provider_switched` | `from`, `to`, `resumed_at`, `speaker_mapping_preserved` | mid-stream failover happened |
196
+ | `done` | `usage.audio_seconds`, `usage.model` | session complete — also resolves `stream.done()` |
197
+ | `error` | `SpeechRouterError` | anything went wrong |
198
+ | `close` | `code?`, `reason?` | socket closed (always last) |
199
+ | `event` | any wire event | firehose — includes `speech_started`, `utterance_end`, `text.delta`, … |
200
+
201
+ **`ListenStream` methods:** `sendAudio(chunk)` (queued if the socket isn't open
202
+ yet) · `finalize()` · `stop()` → usage · `close()` · `done()` · `bufferedAmount`
203
+ · `state` · `session`.
204
+
205
+ ## Errors
206
+
207
+ Everything throws or emits `SpeechRouterError`:
208
+
209
+ ```ts
210
+ try {
211
+ await sr.transcribe({ model: "deepgram/nova-3", file });
212
+ } catch (e) {
213
+ if (e instanceof SpeechRouterError && e.code === "insufficient_credits") topUp();
214
+ }
215
+ ```
216
+
217
+ | `code` | meaning |
218
+ | --- | --- |
219
+ | `auth_failed` / `key_revoked` | bad or revoked API key |
220
+ | `insufficient_credits` | balance empty — top up |
221
+ | `concurrency_exceeded` | too many simultaneous streams for your org |
222
+ | `model_not_found` / `unsupported_capability` / `unsupported_encoding` | request can't be routed as asked |
223
+ | `invalid_request` / `payload_too_large` | malformed input / file over 250 MB |
224
+ | `provider_error` / `provider_timeout` | upstream vendor failed (`e.provider` says which); with fallbacks these become a `provider_switched` instead |
225
+ | `all_providers_failed` | primary and every fallback failed |
226
+ | `audio_timeout` | no audio or keepalive long enough that the gateway hung up |
227
+ | `connection_failed` / `connection_closed` / `timeout` | client-side network conditions |
228
+
229
+ `e.recoverable` hints whether retrying the same request may succeed.
230
+
231
+ ## Self-hosting
232
+
233
+ The gateway is Apache-2.0 and runs anywhere Docker runs. Point the SDK at yours:
234
+
235
+ ```ts
236
+ new SpeechRouter({ apiKey, baseUrl: "http://localhost:8080" });
237
+ ```
238
+
239
+ ## Browsers & mobile: short-lived tokens
240
+
241
+ An API key shipped to a page is public — never do it. Instead, your backend
242
+ mints a short-lived token and hands it to the client:
243
+
244
+ ```ts
245
+ // backend (key stays here)
246
+ const { token } = await sr.createToken({ ttlSeconds: 60 });
247
+
248
+ // client (browser / React Native)
249
+ const client = new SpeechRouter({ apiKey: token });
250
+ const stream = client.listen({ model: "deepgram/nova-3" });
251
+ ```
252
+
253
+ The TTL only limits how long the token can *open* connections — a stream
254
+ that's already running continues past expiry. Default 60s, max 300s.
255
+
256
+ ---
257
+
258
+ Apache-2.0 · [gateway & protocol spec](https://github.com/speech-router/speechrouter) · [console](https://speechrouter.ai) · [live models & pricing](https://speechrouter.ai/models)
259
+
260
+ > `@speechrouter/sdk` is a hard re-export of [`speechrouter`](https://www.npmjs.com/package/speechrouter) — same code, same class identities. Docs use the canonical name.
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@speechrouter/sdk",
3
- "version": "0.1.0",
4
- "description": "One API for every speech model — scoped alias of the `speechrouter` package.",
3
+ "version": "0.3.0",
4
+ "description": "One API for every speech model — official TypeScript SDK for speechrouter.ai. Streaming STT with mid-stream failover, batch transcription. Browser, Node, React Native.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://speechrouter.ai",
7
- "repository": { "type": "git", "url": "git+https://github.com/speech-router/speechrouter.git", "directory": "packages/sdk-js" },
7
+ "repository": { "type": "git", "url": "git+https://github.com/speech-router/speechrouter.git", "directory": "packages/sdk-ts" },
8
+ "keywords": ["speech-to-text", "stt", "transcription", "streaming", "websocket", "deepgram", "whisper", "react-native"],
8
9
  "type": "module",
9
10
  "main": "./index.cjs",
10
11
  "module": "./index.js",
@@ -14,5 +15,6 @@
14
15
  "./mic": { "types": "./mic.d.ts", "import": "./mic.js", "require": "./mic.cjs" }
15
16
  },
16
17
  "files": ["index.js", "index.cjs", "index.d.ts", "mic.js", "mic.cjs", "mic.d.ts", "README.md"],
17
- "dependencies": { "speechrouter": "^0.1.0" }
18
+ "sideEffects": false,
19
+ "dependencies": { "speechrouter": "^0.3.0" }
18
20
  }