@speechrouter/sdk 0.1.1 → 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.
package/README.md CHANGED
@@ -1,17 +1,20 @@
1
- # @speechrouter/sdk
1
+ # speechrouter
2
2
 
3
- One API for every speech model — [speechrouter.ai](https://speechrouter.ai).
3
+ One API for every speech model — [speechrouter.ai](https://speechrouter.ai)
4
4
 
5
- Streaming speech-to-text over WebSocket with mid-stream provider failover,
6
- plus batch transcription. Works in browsers, Node 18, and React Native.
7
- Zero runtime dependencies.
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
8
12
 
9
13
  ```sh
10
- npm install speechrouter # canonical
11
- npm install @speechrouter/sdk # same client, scoped
14
+ npm install speechrouter
12
15
  ```
13
16
 
14
- ## Streaming
17
+ ## Quickstart — live transcription
15
18
 
16
19
  ```ts
17
20
  import { SpeechRouter } from "speechrouter";
@@ -20,76 +23,238 @@ const sr = new SpeechRouter({ apiKey: "sk_sr_..." });
20
23
 
21
24
  const stream = sr.listen({
22
25
  model: "deepgram/nova-3",
23
- fallbacks: ["soniox/stt-rt-v5"], // dies mid-stream? we switch, you keep captioning
26
+ fallbacks: ["soniox/stt-rt-v5"], // primary dies mid-stream? we switch, you keep captioning
24
27
  });
25
28
 
29
+ stream.on("open", (s) => console.log("session", s.session_id));
26
30
  stream.on("transcript", (t) => {
27
- if (t.is_final) console.log(t.text);
31
+ if (t.is_final) console.log("✔", t.text);
32
+ else console.log("…", t.text);
28
33
  });
29
- stream.on("provider_switched", (s) => console.log(`failover: ${s.from} → ${s.to}`));
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
30
40
 
31
- stream.sendAudio(pcmChunk); // 16-bit linear PCM, 16 kHz mono by default
32
- const { usage } = await stream.stop(); // finalize transcript tail usage
41
+ const done = await stream.stop(); // finalize transcript tail usage
42
+ console.log(done.usage); // { audio_seconds: 12.4, model: "deepgram/nova-3" }
33
43
  ```
34
44
 
35
- Or consume the session as an async stream:
45
+ Prefer async iteration? Every session is an `AsyncIterable` of wire events:
36
46
 
37
47
  ```ts
38
48
  for await (const event of stream) {
39
49
  if (event.type === "transcript" && event.is_final) console.log(event.text);
50
+ if (event.type === "provider_switched") console.log("failover!");
40
51
  }
41
52
  ```
42
53
 
43
54
  ## Microphone (browser)
44
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
+
45
60
  ```ts
61
+ import { SpeechRouter } from "speechrouter";
46
62
  import { openMicrophone } from "speechrouter/mic";
47
63
 
48
- const mic = await openMicrophone(stream, { onLevel: (rms) => meter.style.width = `${rms * 300}px` });
49
- // later: mic.stop(); await stream.stop();
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();
50
115
  ```
51
116
 
52
- Captures the default mic, resamples whatever rate the browser gives you
53
- (96 kHz interfaces included) down to 16 kHz PCM, and pumps it into the stream.
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
+ ```
54
132
 
55
- In React Native, capture PCM with a native module (e.g.
56
- `react-native-live-audio-stream`) and call `stream.sendAudio(chunk)` the
57
- core client is RN-safe and never imports browser APIs.
133
+ Node 22+ uses the built-in WebSocket. Node 18–21: `npm install ws`
134
+ (optional peer dependency, picked up automatically).
58
135
 
59
- ## Batch
136
+ ## Batch transcription
60
137
 
61
138
  ```ts
139
+ // simplest: { text }
62
140
  const { text } = await sr.transcribe({ model: "cartesia/ink-whisper", file });
63
141
 
64
- // subtitles straight out:
65
- const srt = await sr.transcribe({ model: "deepgram/nova-3", file, responseFormat: "srt" });
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" });
66
150
  ```
67
151
 
68
- `file` accepts a browser `File`/`Blob`, raw bytes (`Uint8Array`/`ArrayBuffer`),
69
- or a React Native descriptor `{ uri, name, type }`. Pass `url` instead to have
70
- the gateway fetch the audio itself.
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.
71
161
 
72
162
  ## Models
73
163
 
74
164
  ```ts
75
- const models = await sr.listModels(); // slugs, capabilities, live pricing
165
+ const models = await sr.listModels();
166
+ // [{ slug: "deepgram/nova-3", modes: ["streaming","batch"], pricing: {...}, capabilities: {...} }, ...]
76
167
  ```
77
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
+
78
205
  ## Errors
79
206
 
80
- Everything throws or emits `SpeechRouterError` with a machine-readable
81
- `code` (`insufficient_credits`, `concurrency_exceeded`, `provider_error`,
82
- …), the upstream `provider` when known, and a `recoverable` hint.
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.
83
230
 
84
231
  ## Self-hosting
85
232
 
86
- Point the client at your own gateway:
233
+ The gateway is Apache-2.0 and runs anywhere Docker runs. Point the SDK at yours:
87
234
 
88
235
  ```ts
89
236
  new SpeechRouter({ apiKey, baseUrl: "http://localhost:8080" });
90
237
  ```
91
238
 
92
- Note for browsers: an API key shipped to a page is public. Mint short-lived
93
- keys from your backend, or proxy the socket.
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)
94
259
 
95
- Apache-2.0 · [protocol spec](https://github.com/speech-router/speechrouter/tree/main/packages/spec) · [gateway](https://github.com/speech-router/speechrouter)
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/index.cjs ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('speechrouter')
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'speechrouter'
package/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from 'speechrouter'
package/mic.cjs ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('speechrouter/mic')
package/mic.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from 'speechrouter/mic'
package/mic.js ADDED
@@ -0,0 +1 @@
1
+ export * from 'speechrouter/mic'
package/package.json CHANGED
@@ -1,64 +1,20 @@
1
1
  {
2
2
  "name": "@speechrouter/sdk",
3
- "version": "0.1.1",
4
- "description": "One API for every speech model \u2014 official TypeScript SDK for speechrouter.ai. Streaming STT with mid-stream failover, batch transcription. Browser, Node, React Native.",
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": {
8
- "type": "git",
9
- "url": "git+https://github.com/speech-router/speechrouter.git",
10
- "directory": "packages/sdk-js"
11
- },
12
- "keywords": [
13
- "speech-to-text",
14
- "stt",
15
- "transcription",
16
- "streaming",
17
- "websocket",
18
- "deepgram",
19
- "whisper",
20
- "react-native"
21
- ],
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"],
22
9
  "type": "module",
23
- "main": "./dist/index.cjs",
24
- "module": "./dist/index.js",
25
- "types": "./dist/index.d.ts",
10
+ "main": "./index.cjs",
11
+ "module": "./index.js",
12
+ "types": "./index.d.ts",
26
13
  "exports": {
27
- ".": {
28
- "import": {
29
- "types": "./dist/index.d.ts",
30
- "default": "./dist/index.js"
31
- },
32
- "require": {
33
- "types": "./dist/index.d.cts",
34
- "default": "./dist/index.cjs"
35
- }
36
- },
37
- "./mic": {
38
- "import": {
39
- "types": "./dist/mic.d.ts",
40
- "default": "./dist/mic.js"
41
- },
42
- "require": {
43
- "types": "./dist/mic.d.cts",
44
- "default": "./dist/mic.cjs"
45
- }
46
- }
14
+ ".": { "types": "./index.d.ts", "import": "./index.js", "require": "./index.cjs" },
15
+ "./mic": { "types": "./mic.d.ts", "import": "./mic.js", "require": "./mic.cjs" }
47
16
  },
48
- "files": [
49
- "dist",
50
- "README.md"
51
- ],
17
+ "files": ["index.js", "index.cjs", "index.d.ts", "mic.js", "mic.cjs", "mic.d.ts", "README.md"],
52
18
  "sideEffects": false,
53
- "engines": {
54
- "node": ">=18"
55
- },
56
- "peerDependencies": {
57
- "ws": ">=8"
58
- },
59
- "peerDependenciesMeta": {
60
- "ws": {
61
- "optional": true
62
- }
63
- }
64
- }
19
+ "dependencies": { "speechrouter": "^0.3.0" }
20
+ }