@speechrouter/sdk 0.1.0 → 0.1.1

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,10 +1,95 @@
1
1
  # @speechrouter/sdk
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. Works in browsers, Node ≥ 18, and React Native.
7
+ Zero runtime dependencies.
5
8
 
6
9
  ```sh
7
- npm install speechrouter
10
+ npm install speechrouter # canonical
11
+ npm install @speechrouter/sdk # same client, scoped
12
+ ```
13
+
14
+ ## Streaming
15
+
16
+ ```ts
17
+ import { SpeechRouter } from "speechrouter";
18
+
19
+ const sr = new SpeechRouter({ apiKey: "sk_sr_..." });
20
+
21
+ const stream = sr.listen({
22
+ model: "deepgram/nova-3",
23
+ fallbacks: ["soniox/stt-rt-v5"], // dies mid-stream? we switch, you keep captioning
24
+ });
25
+
26
+ stream.on("transcript", (t) => {
27
+ if (t.is_final) console.log(t.text);
28
+ });
29
+ stream.on("provider_switched", (s) => console.log(`failover: ${s.from} → ${s.to}`));
30
+
31
+ stream.sendAudio(pcmChunk); // 16-bit linear PCM, 16 kHz mono by default
32
+ const { usage } = await stream.stop(); // finalize → transcript tail → usage
33
+ ```
34
+
35
+ Or consume the session as an async stream:
36
+
37
+ ```ts
38
+ for await (const event of stream) {
39
+ if (event.type === "transcript" && event.is_final) console.log(event.text);
40
+ }
41
+ ```
42
+
43
+ ## Microphone (browser)
44
+
45
+ ```ts
46
+ import { openMicrophone } from "speechrouter/mic";
47
+
48
+ const mic = await openMicrophone(stream, { onLevel: (rms) => meter.style.width = `${rms * 300}px` });
49
+ // later: mic.stop(); await stream.stop();
50
+ ```
51
+
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.
54
+
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.
58
+
59
+ ## Batch
60
+
61
+ ```ts
62
+ const { text } = await sr.transcribe({ model: "cartesia/ink-whisper", file });
63
+
64
+ // subtitles straight out:
65
+ const srt = await sr.transcribe({ model: "deepgram/nova-3", file, responseFormat: "srt" });
66
+ ```
67
+
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.
71
+
72
+ ## Models
73
+
74
+ ```ts
75
+ const models = await sr.listModels(); // slugs, capabilities, live pricing
76
+ ```
77
+
78
+ ## Errors
79
+
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.
83
+
84
+ ## Self-hosting
85
+
86
+ Point the client at your own gateway:
87
+
88
+ ```ts
89
+ new SpeechRouter({ apiKey, baseUrl: "http://localhost:8080" });
8
90
  ```
9
91
 
10
- Both names ship the same client; `speechrouter` is canonical.
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.
94
+
95
+ Apache-2.0 · [protocol spec](https://github.com/speech-router/speechrouter/tree/main/packages/spec) · [gateway](https://github.com/speech-router/speechrouter)
package/dist/index.cjs ADDED
@@ -0,0 +1,441 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ListenStream: () => ListenStream,
34
+ SpeechRouter: () => SpeechRouter,
35
+ SpeechRouterError: () => SpeechRouterError,
36
+ buildListenUrl: () => buildListenUrl
37
+ });
38
+ module.exports = __toCommonJS(index_exports);
39
+
40
+ // src/errors.ts
41
+ var SpeechRouterError = class extends Error {
42
+ constructor(message, opts) {
43
+ super(message);
44
+ this.name = "SpeechRouterError";
45
+ this.code = opts.code;
46
+ if (opts.status !== void 0) this.status = opts.status;
47
+ if (opts.provider !== void 0) this.provider = opts.provider;
48
+ this.recoverable = opts.recoverable ?? false;
49
+ }
50
+ };
51
+
52
+ // src/ws.ts
53
+ async function resolveWebSocket() {
54
+ const g = globalThis;
55
+ if (typeof g.WebSocket === "function") return g.WebSocket;
56
+ try {
57
+ const mod = await import(
58
+ /* webpackIgnore: true */
59
+ "ws"
60
+ );
61
+ return mod.WebSocket ?? mod.default;
62
+ } catch {
63
+ throw new Error(
64
+ "No WebSocket implementation found. Use Node >= 22, or install the optional peer dependency: npm install ws"
65
+ );
66
+ }
67
+ }
68
+
69
+ // src/listen.ts
70
+ function toQuery(opts, apiKey) {
71
+ const q = new URLSearchParams();
72
+ q.set("model", opts.model);
73
+ if (opts.fallbacks?.length) q.set("fallbacks", opts.fallbacks.join(","));
74
+ q.set("encoding", opts.encoding ?? "linear16");
75
+ q.set("sample_rate", String(opts.sampleRate ?? 16e3));
76
+ q.set("channels", String(opts.channels ?? 1));
77
+ if (opts.language) q.set("language", opts.language);
78
+ if (opts.interimResults === false) q.set("interim_results", "false");
79
+ if (opts.diarization) q.set("diarization", "true");
80
+ if (opts.keyterms?.length) q.set("keyterms", opts.keyterms.join(","));
81
+ if (opts.includeRaw) q.set("include_raw", "true");
82
+ if (opts.providerParams && Object.keys(opts.providerParams).length)
83
+ q.set("provider_params", JSON.stringify(opts.providerParams));
84
+ q.set("api_key", apiKey);
85
+ return q.toString();
86
+ }
87
+ function buildListenUrl(wsBase, opts, apiKey) {
88
+ return `${wsBase}/v1/listen?${toQuery(opts, apiKey)}`;
89
+ }
90
+ var ListenStream = class {
91
+ constructor(url, opts) {
92
+ this.url = url;
93
+ this.opts = opts;
94
+ this.ws = null;
95
+ this.listeners = /* @__PURE__ */ new Map();
96
+ this.sendQueue = [];
97
+ this.iterQueue = [];
98
+ this.iterWaiter = null;
99
+ this.keepAliveTimer = null;
100
+ this.connectTimer = null;
101
+ this.doneSettled = false;
102
+ /** 'connecting' → 'open' → 'closed'; 'finalizing' between finalize() and done. */
103
+ this.state = "connecting";
104
+ /** Set once the gateway confirms the session. */
105
+ this.session = null;
106
+ this.donePromise = new Promise((resolve, reject) => {
107
+ this.resolveDone = resolve;
108
+ this.rejectDone = reject;
109
+ });
110
+ this.donePromise.catch(() => {
111
+ });
112
+ void this.connect();
113
+ }
114
+ /* ---- event plumbing ------------------------------------------------ */
115
+ on(type, fn) {
116
+ let set = this.listeners.get(type);
117
+ if (!set) this.listeners.set(type, set = /* @__PURE__ */ new Set());
118
+ set.add(fn);
119
+ return () => set.delete(fn);
120
+ }
121
+ once(type, fn) {
122
+ const off = this.on(type, (e) => {
123
+ off();
124
+ fn(e);
125
+ });
126
+ return off;
127
+ }
128
+ emit(type, event) {
129
+ this.listeners.get(type)?.forEach((fn) => fn(event));
130
+ }
131
+ /** Consume the session as an async stream of wire events. */
132
+ [Symbol.asyncIterator]() {
133
+ return {
134
+ next: () => {
135
+ const queued = this.iterQueue.shift();
136
+ if (queued) return Promise.resolve({ value: queued, done: false });
137
+ if (this.state === "closed") return Promise.resolve({ value: void 0, done: true });
138
+ return new Promise((resolve) => this.iterWaiter = resolve);
139
+ },
140
+ return: () => {
141
+ this.close();
142
+ return Promise.resolve({ value: void 0, done: true });
143
+ }
144
+ };
145
+ }
146
+ /* ---- lifecycle ----------------------------------------------------- */
147
+ async connect() {
148
+ let WS;
149
+ try {
150
+ WS = await resolveWebSocket();
151
+ this.ws = new WS(this.url);
152
+ } catch (err) {
153
+ this.fail(
154
+ new SpeechRouterError(err instanceof Error ? err.message : "could not open socket", {
155
+ code: "connection_failed"
156
+ })
157
+ );
158
+ return;
159
+ }
160
+ const ws = this.ws;
161
+ ws.binaryType = "arraybuffer";
162
+ const timeoutMs = this.opts.connectTimeoutMs ?? 1e4;
163
+ this.connectTimer = setTimeout(() => {
164
+ if (this.state === "connecting") {
165
+ this.fail(new SpeechRouterError(`socket not open after ${timeoutMs}ms`, { code: "timeout" }));
166
+ try {
167
+ ws.close();
168
+ } catch {
169
+ }
170
+ }
171
+ }, timeoutMs);
172
+ ws.addEventListener("open", () => {
173
+ if (this.connectTimer) clearTimeout(this.connectTimer);
174
+ if (this.state !== "connecting") return;
175
+ this.state = "open";
176
+ for (const chunk of this.sendQueue) ws.send(chunk);
177
+ this.sendQueue = [];
178
+ this.startKeepAlive();
179
+ });
180
+ ws.addEventListener("message", (e) => {
181
+ void this.handleMessage(e.data);
182
+ });
183
+ ws.addEventListener("error", () => {
184
+ if (this.state === "connecting") {
185
+ this.fail(new SpeechRouterError("websocket connection failed", { code: "connection_failed" }));
186
+ }
187
+ });
188
+ ws.addEventListener("close", (e) => {
189
+ this.teardown(e.code, e.reason);
190
+ });
191
+ }
192
+ async handleMessage(data) {
193
+ let text;
194
+ if (typeof data === "string") text = data;
195
+ else if (data instanceof ArrayBuffer) text = new TextDecoder().decode(data);
196
+ else if (typeof data?.text === "function") text = await data.text();
197
+ else return;
198
+ let event;
199
+ try {
200
+ event = JSON.parse(text);
201
+ } catch {
202
+ return;
203
+ }
204
+ this.emit("event", event);
205
+ this.pushIter(event);
206
+ switch (event.type) {
207
+ case "session.open":
208
+ this.session = event;
209
+ this.emit("open", event);
210
+ break;
211
+ case "transcript":
212
+ this.emit("transcript", event);
213
+ break;
214
+ case "provider_switched":
215
+ this.emit("provider_switched", event);
216
+ break;
217
+ case "done":
218
+ this.settleDone(event);
219
+ this.emit("done", event);
220
+ break;
221
+ case "error": {
222
+ const err = new SpeechRouterError(event.message, {
223
+ code: event.code,
224
+ recoverable: event.recoverable ?? false,
225
+ ...event.provider !== void 0 ? { provider: event.provider } : {}
226
+ });
227
+ if (!event.recoverable) this.settleDoneWith(err);
228
+ this.emit("error", err);
229
+ break;
230
+ }
231
+ }
232
+ }
233
+ pushIter(event) {
234
+ if (this.iterWaiter) {
235
+ const w = this.iterWaiter;
236
+ this.iterWaiter = null;
237
+ w({ value: event, done: false });
238
+ } else {
239
+ this.iterQueue.push(event);
240
+ }
241
+ }
242
+ startKeepAlive() {
243
+ const setting = this.opts.keepAlive ?? true;
244
+ if (setting === false) return;
245
+ const interval = typeof setting === "number" ? setting : 8e3;
246
+ this.keepAliveTimer = setInterval(() => {
247
+ if (this.state === "open") this.sendJson({ type: "keepalive" });
248
+ }, interval);
249
+ }
250
+ fail(err) {
251
+ this.settleDoneWith(err);
252
+ this.emit("error", err);
253
+ this.teardown();
254
+ }
255
+ settleDone(d) {
256
+ if (!this.doneSettled) {
257
+ this.doneSettled = true;
258
+ this.resolveDone(d);
259
+ }
260
+ }
261
+ settleDoneWith(err) {
262
+ if (!this.doneSettled) {
263
+ this.doneSettled = true;
264
+ this.rejectDone(err);
265
+ }
266
+ }
267
+ teardown(code, reason) {
268
+ if (this.state === "closed") return;
269
+ this.state = "closed";
270
+ if (this.keepAliveTimer) clearInterval(this.keepAliveTimer);
271
+ if (this.connectTimer) clearTimeout(this.connectTimer);
272
+ this.settleDoneWith(
273
+ new SpeechRouterError("connection closed before the session finished", {
274
+ code: "connection_closed"
275
+ })
276
+ );
277
+ if (this.iterWaiter) {
278
+ const w = this.iterWaiter;
279
+ this.iterWaiter = null;
280
+ w({ value: void 0, done: true });
281
+ }
282
+ this.emit("close", {
283
+ ...code !== void 0 ? { code } : {},
284
+ ...reason !== void 0 ? { reason } : {}
285
+ });
286
+ }
287
+ /* ---- outbound ------------------------------------------------------ */
288
+ /** Send a chunk of PCM audio. Chunks sent before the socket opens are queued. */
289
+ sendAudio(chunk) {
290
+ if (this.state === "closed" || this.state === "finalizing")
291
+ throw new SpeechRouterError("cannot send audio: stream is " + this.state, {
292
+ code: "connection_closed"
293
+ });
294
+ if (this.state === "connecting") {
295
+ this.sendQueue.push(chunk);
296
+ return;
297
+ }
298
+ this.ws.send(chunk);
299
+ }
300
+ /** Bytes accepted but not yet on the wire — use to pace large sends. */
301
+ get bufferedAmount() {
302
+ return this.ws?.bufferedAmount ?? 0;
303
+ }
304
+ sendJson(msg) {
305
+ if (this.state === "open" || this.state === "finalizing") this.ws.send(JSON.stringify(msg));
306
+ }
307
+ /** Ask the gateway to flush pending audio into a final transcript. */
308
+ finalize() {
309
+ if (this.state === "open") {
310
+ this.state = "finalizing";
311
+ this.sendJson({ type: "finalize" });
312
+ }
313
+ }
314
+ /** Resolves with the gateway's usage summary once the session completes. */
315
+ done() {
316
+ return this.donePromise;
317
+ }
318
+ /**
319
+ * Graceful shutdown: finalize, wait for the `done` usage event, close the
320
+ * socket. Returns the done event; rejects if the session errored.
321
+ */
322
+ async stop(timeoutMs = 3e4) {
323
+ this.finalize();
324
+ const timeout = new Promise(
325
+ (_, reject) => setTimeout(
326
+ () => reject(new SpeechRouterError("gave up waiting for done", { code: "timeout" })),
327
+ timeoutMs
328
+ )
329
+ );
330
+ try {
331
+ return await Promise.race([this.donePromise, timeout]);
332
+ } finally {
333
+ this.close();
334
+ }
335
+ }
336
+ /** Immediate shutdown. In-flight audio may go untranscribed — prefer stop(). */
337
+ close() {
338
+ try {
339
+ this.ws?.close(1e3);
340
+ } catch {
341
+ }
342
+ this.teardown();
343
+ }
344
+ };
345
+
346
+ // src/client.ts
347
+ var DEFAULT_BASE = "https://api.speechrouter.ai";
348
+ var SpeechRouter = class {
349
+ constructor(opts) {
350
+ if (!opts.apiKey) throw new SpeechRouterError("apiKey is required", { code: "auth_failed" });
351
+ this.apiKey = opts.apiKey;
352
+ this.base = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/+$/, "");
353
+ this.wsBase = this.base.replace(/^http/, "ws");
354
+ this.fetchImpl = opts.fetch ?? globalThis.fetch?.bind(globalThis);
355
+ if (!this.fetchImpl)
356
+ throw new SpeechRouterError("no fetch available in this runtime", { code: "internal_error" });
357
+ }
358
+ /** Open a live transcription session over WebSocket. */
359
+ listen(opts) {
360
+ return new ListenStream(buildListenUrl(this.wsBase, opts, this.apiKey), opts);
361
+ }
362
+ async transcribe(opts) {
363
+ const form = new FormData();
364
+ form.set("model", opts.model);
365
+ if (opts.responseFormat) form.set("response_format", opts.responseFormat);
366
+ if (opts.language) form.set("language", opts.language);
367
+ if (opts.diarization) form.set("diarization", "true");
368
+ if (opts.keyterms?.length) form.set("keyterms", opts.keyterms.join(","));
369
+ if (opts.includeRaw) form.set("include_raw", "true");
370
+ if (opts.providerParams && Object.keys(opts.providerParams).length)
371
+ form.set("provider_params", JSON.stringify(opts.providerParams));
372
+ if (opts.url) {
373
+ form.set("url", opts.url);
374
+ } else if (opts.file !== void 0) {
375
+ form.set("file", this.toFormPart(opts.file), this.partName(opts.file, opts.filename));
376
+ } else {
377
+ throw new SpeechRouterError("transcribe needs a file or a url", { code: "invalid_request" });
378
+ }
379
+ const response = await this.request("/v1/audio/transcriptions", {
380
+ method: "POST",
381
+ body: form
382
+ });
383
+ const contentType = response.headers.get("content-type") ?? "";
384
+ if (contentType.includes("application/json")) return response.json();
385
+ return response.text();
386
+ }
387
+ /** The live model catalog — slugs, capabilities, pricing. */
388
+ async listModels() {
389
+ const response = await this.request("/v1/models", { method: "GET" });
390
+ const payload = await response.json();
391
+ return payload.data ?? [];
392
+ }
393
+ /* ---- internals ----------------------------------------------------- */
394
+ toFormPart(file) {
395
+ if (file instanceof Uint8Array)
396
+ return new Blob([file.slice().buffer]);
397
+ if (file instanceof ArrayBuffer) return new Blob([file]);
398
+ return file;
399
+ }
400
+ partName(file, filename) {
401
+ if (filename) return filename;
402
+ if (typeof File !== "undefined" && file instanceof File) return file.name;
403
+ return "audio";
404
+ }
405
+ async request(path, init) {
406
+ let response;
407
+ try {
408
+ response = await this.fetchImpl(`${this.base}${path}`, {
409
+ ...init,
410
+ headers: { Authorization: `Bearer ${this.apiKey}` }
411
+ });
412
+ } catch (err) {
413
+ throw new SpeechRouterError(
414
+ err instanceof Error ? err.message : "network request failed",
415
+ { code: "connection_failed" }
416
+ );
417
+ }
418
+ if (response.ok) return response;
419
+ let code = "internal_error";
420
+ let message = `HTTP ${response.status}`;
421
+ try {
422
+ const body = await response.json();
423
+ if (body.error?.code) code = body.error.code;
424
+ if (body.error?.message) message = body.error.message;
425
+ } catch {
426
+ }
427
+ throw new SpeechRouterError(message, {
428
+ code,
429
+ status: response.status,
430
+ recoverable: response.status >= 500
431
+ });
432
+ }
433
+ };
434
+ // Annotate the CommonJS export names for ESM import in node:
435
+ 0 && (module.exports = {
436
+ ListenStream,
437
+ SpeechRouter,
438
+ SpeechRouterError,
439
+ buildListenUrl
440
+ });
441
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/ws.ts","../src/listen.ts","../src/client.ts"],"sourcesContent":["export { SpeechRouter } from './client'\nexport type { SpeechRouterOptions, TranscribeOptions, FileInput } from './client'\nexport { ListenStream, buildListenUrl } from './listen'\nexport type { ListenOptions, ListenEventMap } from './listen'\nexport { SpeechRouterError } from './errors'\nexport type {\n ListenEvent,\n SessionOpenEvent,\n TranscriptEvent,\n SpeechStartedEvent,\n UtteranceEndEvent,\n ProviderSwitchedEvent,\n TextDeltaEvent,\n ClearedEvent,\n KeepAliveEvent,\n DoneEvent,\n ErrorEvent,\n ErrorCode,\n Word,\n Model,\n Transcription,\n VerboseTranscription,\n} from './events'\n","import type { ErrorCode } from './events'\n\n/** Every failure the SDK surfaces is one of these. */\nexport class SpeechRouterError extends Error {\n /** Machine-readable code from the gateway's 16-code error enum, or a\n * client-side code ('connection_failed', 'connection_closed', 'timeout'). */\n readonly code: ErrorCode | 'connection_failed' | 'connection_closed' | 'timeout'\n /** HTTP status for REST calls; undefined for WebSocket errors. */\n readonly status?: number\n /** Which upstream provider tripped, when the gateway says. */\n readonly provider?: string\n /** Gateway's hint that retrying the same request may succeed. */\n readonly recoverable: boolean\n\n constructor(\n message: string,\n opts: {\n code: SpeechRouterError['code']\n status?: number\n provider?: string\n recoverable?: boolean\n },\n ) {\n super(message)\n this.name = 'SpeechRouterError'\n this.code = opts.code\n if (opts.status !== undefined) this.status = opts.status\n if (opts.provider !== undefined) this.provider = opts.provider\n this.recoverable = opts.recoverable ?? false\n }\n}\n","/* Runtime-adaptive WebSocket: browsers and React Native have a global;\n * Node 22+ ships one (undici); older Node falls back to the optional `ws`\n * peer dependency. Everything downstream codes against the browser API. */\n\nexport interface WSLike {\n binaryType: string\n readonly readyState: number\n readonly bufferedAmount?: number\n send(data: string | ArrayBufferLike | ArrayBufferView): void\n close(code?: number, reason?: string): void\n addEventListener(type: string, listener: (event: any) => void): void\n}\n\nexport type WSConstructor = new (url: string) => WSLike\n\nexport async function resolveWebSocket(): Promise<WSConstructor> {\n const g = globalThis as Record<string, unknown>\n if (typeof g.WebSocket === 'function') return g.WebSocket as WSConstructor\n try {\n const mod = await import(/* webpackIgnore: true */ 'ws')\n return (mod.WebSocket ?? mod.default) as WSConstructor\n } catch {\n throw new Error(\n 'No WebSocket implementation found. Use Node >= 22, or install the optional peer dependency: npm install ws',\n )\n }\n}\n","import { SpeechRouterError } from './errors'\nimport type {\n DoneEvent,\n ListenEvent,\n ProviderSwitchedEvent,\n SessionOpenEvent,\n TranscriptEvent,\n} from './events'\nimport { resolveWebSocket, type WSLike } from './ws'\n\nexport interface ListenOptions {\n /** Model slug, e.g. \"deepgram/nova-3\". */\n model: string\n /** Ordered failover lane, e.g. [\"soniox/stt-rt-v5\"]. */\n fallbacks?: string[]\n /** PCM encoding of the audio you will send. Default \"linear16\". */\n encoding?: string\n /** Sample rate of the audio you will send. Default 16000. */\n sampleRate?: number\n /** Channel count. Default 1. */\n channels?: number\n language?: string\n /** Emit non-final hypotheses. Default true. */\n interimResults?: boolean\n diarization?: boolean\n /** Bias recognition toward these terms (when the model supports it). */\n keyterms?: string[]\n /** Attach the untouched provider payload to every transcript. */\n includeRaw?: boolean\n /** Escape hatch: raw params forwarded to the provider. */\n providerParams?: Record<string, unknown>\n /** Abort the dial if the socket is not open in this many ms. Default 10000. */\n connectTimeoutMs?: number\n /**\n * Keep the session alive through silences by sending keepalive frames.\n * true = every 8000 ms, a number = that interval, false = off (default\n * true). Note: an open session bills wall-clock time on session-billed\n * providers — close streams you are done with.\n */\n keepAlive?: boolean | number\n}\n\ntype Listener<E> = (event: E) => void\n\nexport interface ListenEventMap {\n /** Socket is open and the gateway accepted the session. */\n open: SessionOpenEvent\n transcript: TranscriptEvent\n provider_switched: ProviderSwitchedEvent\n done: DoneEvent\n error: SpeechRouterError\n /** Fired exactly once, after every other event. */\n close: { code?: number; reason?: string }\n /** Every wire event, untouched — including ones without a named channel. */\n event: ListenEvent\n}\n\nconst CLIENT_CODES = new Set([\n 'connection_failed',\n 'connection_closed',\n 'timeout',\n])\n\nfunction toQuery(opts: ListenOptions, apiKey: string): string {\n const q = new URLSearchParams()\n q.set('model', opts.model)\n if (opts.fallbacks?.length) q.set('fallbacks', opts.fallbacks.join(','))\n q.set('encoding', opts.encoding ?? 'linear16')\n q.set('sample_rate', String(opts.sampleRate ?? 16000))\n q.set('channels', String(opts.channels ?? 1))\n if (opts.language) q.set('language', opts.language)\n if (opts.interimResults === false) q.set('interim_results', 'false')\n if (opts.diarization) q.set('diarization', 'true')\n if (opts.keyterms?.length) q.set('keyterms', opts.keyterms.join(','))\n if (opts.includeRaw) q.set('include_raw', 'true')\n if (opts.providerParams && Object.keys(opts.providerParams).length)\n q.set('provider_params', JSON.stringify(opts.providerParams))\n q.set('api_key', apiKey)\n return q.toString()\n}\n\nexport function buildListenUrl(wsBase: string, opts: ListenOptions, apiKey: string): string {\n return `${wsBase}/v1/listen?${toQuery(opts, apiKey)}`\n}\n\n/**\n * A live transcription session. Create via `client.listen(...)`, then send\n * PCM with `sendAudio()` and consume events with `on()` or `for await`.\n */\nexport class ListenStream {\n private ws: WSLike | null = null\n private listeners = new Map<keyof ListenEventMap, Set<Listener<any>>>()\n private sendQueue: (ArrayBufferLike | ArrayBufferView)[] = []\n private iterQueue: ListenEvent[] = []\n private iterWaiter: ((r: IteratorResult<ListenEvent>) => void) | null = null\n private keepAliveTimer: ReturnType<typeof setInterval> | null = null\n private connectTimer: ReturnType<typeof setTimeout> | null = null\n private donePromise: Promise<DoneEvent>\n private resolveDone!: (d: DoneEvent) => void\n private rejectDone!: (e: SpeechRouterError) => void\n private doneSettled = false\n\n /** 'connecting' → 'open' → 'closed'; 'finalizing' between finalize() and done. */\n state: 'connecting' | 'open' | 'finalizing' | 'closed' = 'connecting'\n /** Set once the gateway confirms the session. */\n session: SessionOpenEvent | null = null\n\n constructor(private url: string, private opts: ListenOptions) {\n this.donePromise = new Promise((resolve, reject) => {\n this.resolveDone = resolve\n this.rejectDone = reject\n })\n // A caller may only await done(); don't let that surface as unhandled.\n this.donePromise.catch(() => {})\n void this.connect()\n }\n\n /* ---- event plumbing ------------------------------------------------ */\n\n on<K extends keyof ListenEventMap>(type: K, fn: Listener<ListenEventMap[K]>): () => void {\n let set = this.listeners.get(type)\n if (!set) this.listeners.set(type, (set = new Set()))\n set.add(fn)\n return () => set!.delete(fn)\n }\n\n once<K extends keyof ListenEventMap>(type: K, fn: Listener<ListenEventMap[K]>): () => void {\n const off = this.on(type, (e) => {\n off()\n fn(e)\n })\n return off\n }\n\n private emit<K extends keyof ListenEventMap>(type: K, event: ListenEventMap[K]): void {\n this.listeners.get(type)?.forEach((fn) => fn(event))\n }\n\n /** Consume the session as an async stream of wire events. */\n [Symbol.asyncIterator](): AsyncIterator<ListenEvent> {\n return {\n next: (): Promise<IteratorResult<ListenEvent>> => {\n const queued = this.iterQueue.shift()\n if (queued) return Promise.resolve({ value: queued, done: false })\n if (this.state === 'closed') return Promise.resolve({ value: undefined, done: true })\n return new Promise((resolve) => (this.iterWaiter = resolve))\n },\n return: (): Promise<IteratorResult<ListenEvent>> => {\n this.close()\n return Promise.resolve({ value: undefined, done: true })\n },\n }\n }\n\n /* ---- lifecycle ----------------------------------------------------- */\n\n private async connect(): Promise<void> {\n let WS\n try {\n WS = await resolveWebSocket()\n this.ws = new WS(this.url)\n } catch (err) {\n this.fail(\n new SpeechRouterError(err instanceof Error ? err.message : 'could not open socket', {\n code: 'connection_failed',\n }),\n )\n return\n }\n const ws = this.ws\n ws.binaryType = 'arraybuffer'\n\n const timeoutMs = this.opts.connectTimeoutMs ?? 10_000\n this.connectTimer = setTimeout(() => {\n if (this.state === 'connecting') {\n this.fail(new SpeechRouterError(`socket not open after ${timeoutMs}ms`, { code: 'timeout' }))\n try {\n ws.close()\n } catch {\n /* already dead */\n }\n }\n }, timeoutMs)\n\n ws.addEventListener('open', () => {\n if (this.connectTimer) clearTimeout(this.connectTimer)\n if (this.state !== 'connecting') return\n this.state = 'open'\n for (const chunk of this.sendQueue) ws.send(chunk)\n this.sendQueue = []\n this.startKeepAlive()\n })\n\n ws.addEventListener('message', (e: { data: unknown }) => {\n void this.handleMessage(e.data)\n })\n\n ws.addEventListener('error', () => {\n // The close event carries the useful signal; error objects here are\n // implementation-specific noise. Connection-phase failures reject fast:\n if (this.state === 'connecting') {\n this.fail(new SpeechRouterError('websocket connection failed', { code: 'connection_failed' }))\n }\n })\n\n ws.addEventListener('close', (e: { code?: number; reason?: string }) => {\n this.teardown(e.code, e.reason)\n })\n }\n\n private async handleMessage(data: unknown): Promise<void> {\n let text: string\n if (typeof data === 'string') text = data\n else if (data instanceof ArrayBuffer) text = new TextDecoder().decode(data)\n else if (typeof (data as Blob)?.text === 'function') text = await (data as Blob).text()\n else return\n let event: ListenEvent\n try {\n event = JSON.parse(text) as ListenEvent\n } catch {\n return\n }\n\n this.emit('event', event)\n this.pushIter(event)\n\n switch (event.type) {\n case 'session.open':\n this.session = event\n this.emit('open', event)\n break\n case 'transcript':\n this.emit('transcript', event)\n break\n case 'provider_switched':\n this.emit('provider_switched', event)\n break\n case 'done':\n this.settleDone(event)\n this.emit('done', event)\n break\n case 'error': {\n const err = new SpeechRouterError(event.message, {\n code: event.code,\n recoverable: event.recoverable ?? false,\n ...(event.provider !== undefined ? { provider: event.provider } : {}),\n })\n if (!event.recoverable) this.settleDoneWith(err)\n this.emit('error', err)\n break\n }\n }\n }\n\n private pushIter(event: ListenEvent): void {\n if (this.iterWaiter) {\n const w = this.iterWaiter\n this.iterWaiter = null\n w({ value: event, done: false })\n } else {\n this.iterQueue.push(event)\n }\n }\n\n private startKeepAlive(): void {\n const setting = this.opts.keepAlive ?? true\n if (setting === false) return\n const interval = typeof setting === 'number' ? setting : 8000\n this.keepAliveTimer = setInterval(() => {\n if (this.state === 'open') this.sendJson({ type: 'keepalive' })\n }, interval)\n }\n\n private fail(err: SpeechRouterError): void {\n this.settleDoneWith(err)\n this.emit('error', err)\n this.teardown()\n }\n\n private settleDone(d: DoneEvent): void {\n if (!this.doneSettled) {\n this.doneSettled = true\n this.resolveDone(d)\n }\n }\n\n private settleDoneWith(err: SpeechRouterError): void {\n if (!this.doneSettled) {\n this.doneSettled = true\n this.rejectDone(err)\n }\n }\n\n private teardown(code?: number, reason?: string): void {\n if (this.state === 'closed') return\n this.state = 'closed'\n if (this.keepAliveTimer) clearInterval(this.keepAliveTimer)\n if (this.connectTimer) clearTimeout(this.connectTimer)\n this.settleDoneWith(\n new SpeechRouterError('connection closed before the session finished', {\n code: 'connection_closed',\n }),\n )\n if (this.iterWaiter) {\n const w = this.iterWaiter\n this.iterWaiter = null\n w({ value: undefined, done: true })\n }\n this.emit('close', {\n ...(code !== undefined ? { code } : {}),\n ...(reason !== undefined ? { reason } : {}),\n })\n }\n\n /* ---- outbound ------------------------------------------------------ */\n\n /** Send a chunk of PCM audio. Chunks sent before the socket opens are queued. */\n sendAudio(chunk: ArrayBufferLike | ArrayBufferView): void {\n if (this.state === 'closed' || this.state === 'finalizing')\n throw new SpeechRouterError('cannot send audio: stream is ' + this.state, {\n code: 'connection_closed',\n })\n if (this.state === 'connecting') {\n this.sendQueue.push(chunk)\n return\n }\n this.ws!.send(chunk)\n }\n\n /** Bytes accepted but not yet on the wire — use to pace large sends. */\n get bufferedAmount(): number {\n return this.ws?.bufferedAmount ?? 0\n }\n\n private sendJson(msg: Record<string, unknown>): void {\n if (this.state === 'open' || this.state === 'finalizing') this.ws!.send(JSON.stringify(msg))\n }\n\n /** Ask the gateway to flush pending audio into a final transcript. */\n finalize(): void {\n if (this.state === 'open') {\n this.state = 'finalizing'\n this.sendJson({ type: 'finalize' })\n }\n }\n\n /** Resolves with the gateway's usage summary once the session completes. */\n done(): Promise<DoneEvent> {\n return this.donePromise\n }\n\n /**\n * Graceful shutdown: finalize, wait for the `done` usage event, close the\n * socket. Returns the done event; rejects if the session errored.\n */\n async stop(timeoutMs = 30_000): Promise<DoneEvent> {\n this.finalize()\n const timeout = new Promise<never>((_, reject) =>\n setTimeout(\n () => reject(new SpeechRouterError('gave up waiting for done', { code: 'timeout' })),\n timeoutMs,\n ),\n )\n try {\n return await Promise.race([this.donePromise, timeout])\n } finally {\n this.close()\n }\n }\n\n /** Immediate shutdown. In-flight audio may go untranscribed — prefer stop(). */\n close(): void {\n try {\n this.ws?.close(1000)\n } catch {\n /* already closed */\n }\n this.teardown()\n }\n}\n\nexport function isClientCode(code: string): boolean {\n return CLIENT_CODES.has(code)\n}\n","import { SpeechRouterError } from './errors'\nimport type { ErrorCode, Model, Transcription, VerboseTranscription } from './events'\nimport { buildListenUrl, ListenStream, type ListenOptions } from './listen'\n\nexport interface SpeechRouterOptions {\n /** Your sk_sr_... key. In browsers, prefer a short-lived key minted by\n * your backend — anything shipped to a page is public. */\n apiKey: string\n /** Override for self-hosted gateways, e.g. \"http://localhost:8080\". */\n baseUrl?: string\n /** Custom fetch (tests, polyfills). Defaults to globalThis.fetch. */\n fetch?: typeof fetch\n}\n\n/** File input accepted by transcribe(): browser File/Blob, raw bytes, or a\n * React Native file descriptor ({ uri, name, type }). */\nexport type FileInput =\n | Blob\n | ArrayBuffer\n | Uint8Array\n | { uri: string; name: string; type: string }\n\nexport interface TranscribeOptions {\n model: string\n file?: FileInput\n /** Let the gateway fetch the audio itself instead of uploading. */\n url?: string\n /** Filename hint when passing raw bytes. Default \"audio\". */\n filename?: string\n language?: string\n diarization?: boolean\n keyterms?: string[]\n includeRaw?: boolean\n providerParams?: Record<string, unknown>\n}\n\nconst DEFAULT_BASE = 'https://api.speechrouter.ai'\n\nexport class SpeechRouter {\n private apiKey: string\n private base: string\n private wsBase: string\n private fetchImpl: typeof fetch\n\n constructor(opts: SpeechRouterOptions) {\n if (!opts.apiKey) throw new SpeechRouterError('apiKey is required', { code: 'auth_failed' })\n this.apiKey = opts.apiKey\n this.base = (opts.baseUrl ?? DEFAULT_BASE).replace(/\\/+$/, '')\n this.wsBase = this.base.replace(/^http/, 'ws')\n this.fetchImpl = opts.fetch ?? globalThis.fetch?.bind(globalThis)\n if (!this.fetchImpl)\n throw new SpeechRouterError('no fetch available in this runtime', { code: 'internal_error' })\n }\n\n /** Open a live transcription session over WebSocket. */\n listen(opts: ListenOptions): ListenStream {\n return new ListenStream(buildListenUrl(this.wsBase, opts, this.apiKey), opts)\n }\n\n /** Transcribe a complete file. Returns `{ text }`. */\n async transcribe(opts: TranscribeOptions): Promise<Transcription>\n async transcribe(\n opts: TranscribeOptions & { responseFormat: 'verbose_json' },\n ): Promise<VerboseTranscription>\n async transcribe(\n opts: TranscribeOptions & { responseFormat: 'srt' | 'vtt' | 'text' },\n ): Promise<string>\n async transcribe(\n opts: TranscribeOptions & { responseFormat?: string },\n ): Promise<Transcription | VerboseTranscription | string> {\n const form = new FormData()\n form.set('model', opts.model)\n if (opts.responseFormat) form.set('response_format', opts.responseFormat)\n if (opts.language) form.set('language', opts.language)\n if (opts.diarization) form.set('diarization', 'true')\n if (opts.keyterms?.length) form.set('keyterms', opts.keyterms.join(','))\n if (opts.includeRaw) form.set('include_raw', 'true')\n if (opts.providerParams && Object.keys(opts.providerParams).length)\n form.set('provider_params', JSON.stringify(opts.providerParams))\n\n if (opts.url) {\n form.set('url', opts.url)\n } else if (opts.file !== undefined) {\n form.set('file', this.toFormPart(opts.file), this.partName(opts.file, opts.filename))\n } else {\n throw new SpeechRouterError('transcribe needs a file or a url', { code: 'invalid_request' })\n }\n\n const response = await this.request('/v1/audio/transcriptions', {\n method: 'POST',\n body: form,\n })\n const contentType = response.headers.get('content-type') ?? ''\n if (contentType.includes('application/json')) return response.json()\n return response.text()\n }\n\n /** The live model catalog — slugs, capabilities, pricing. */\n async listModels(): Promise<Model[]> {\n const response = await this.request('/v1/models', { method: 'GET' })\n const payload = (await response.json()) as { data?: Model[] }\n return payload.data ?? []\n }\n\n /* ---- internals ----------------------------------------------------- */\n\n private toFormPart(file: FileInput): Blob {\n if (file instanceof Uint8Array)\n // Copy into a fresh ArrayBuffer so SharedArrayBuffer-backed views are boxed too.\n return new Blob([file.slice().buffer])\n if (file instanceof ArrayBuffer) return new Blob([file])\n // RN descriptor rides FormData natively; the cast keeps web types happy.\n return file as Blob\n }\n\n private partName(file: FileInput, filename?: string): string {\n if (filename) return filename\n if (typeof File !== 'undefined' && file instanceof File) return file.name\n return 'audio'\n }\n\n private async request(path: string, init: RequestInit): Promise<Response> {\n let response: Response\n try {\n response = await this.fetchImpl(`${this.base}${path}`, {\n ...init,\n headers: { Authorization: `Bearer ${this.apiKey}` },\n })\n } catch (err) {\n throw new SpeechRouterError(\n err instanceof Error ? err.message : 'network request failed',\n { code: 'connection_failed' },\n )\n }\n if (response.ok) return response\n\n let code: ErrorCode = 'internal_error'\n let message = `HTTP ${response.status}`\n try {\n const body = (await response.json()) as {\n error?: { code?: ErrorCode; message?: string }\n }\n if (body.error?.code) code = body.error.code\n if (body.error?.message) message = body.error.message\n } catch {\n /* non-JSON error body; keep the status line */\n }\n throw new SpeechRouterError(message, {\n code,\n status: response.status,\n recoverable: response.status >= 500,\n })\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,56 @@
1
+ import { L as ListenOptions, a as ListenStream, T as Transcription, V as VerboseTranscription, M as Model } from './listen-DEvmsIUS.cjs';
2
+ export { C as ClearedEvent, D as DoneEvent, E as ErrorCode, b as ErrorEvent, K as KeepAliveEvent, c as ListenEvent, d as ListenEventMap, P as ProviderSwitchedEvent, S as SessionOpenEvent, e as SpeechRouterError, f as SpeechStartedEvent, g as TextDeltaEvent, h as TranscriptEvent, U as UtteranceEndEvent, W as Word, i as buildListenUrl } from './listen-DEvmsIUS.cjs';
3
+
4
+ interface SpeechRouterOptions {
5
+ /** Your sk_sr_... key. In browsers, prefer a short-lived key minted by
6
+ * your backend — anything shipped to a page is public. */
7
+ apiKey: string;
8
+ /** Override for self-hosted gateways, e.g. "http://localhost:8080". */
9
+ baseUrl?: string;
10
+ /** Custom fetch (tests, polyfills). Defaults to globalThis.fetch. */
11
+ fetch?: typeof fetch;
12
+ }
13
+ /** File input accepted by transcribe(): browser File/Blob, raw bytes, or a
14
+ * React Native file descriptor ({ uri, name, type }). */
15
+ type FileInput = Blob | ArrayBuffer | Uint8Array | {
16
+ uri: string;
17
+ name: string;
18
+ type: string;
19
+ };
20
+ interface TranscribeOptions {
21
+ model: string;
22
+ file?: FileInput;
23
+ /** Let the gateway fetch the audio itself instead of uploading. */
24
+ url?: string;
25
+ /** Filename hint when passing raw bytes. Default "audio". */
26
+ filename?: string;
27
+ language?: string;
28
+ diarization?: boolean;
29
+ keyterms?: string[];
30
+ includeRaw?: boolean;
31
+ providerParams?: Record<string, unknown>;
32
+ }
33
+ declare class SpeechRouter {
34
+ private apiKey;
35
+ private base;
36
+ private wsBase;
37
+ private fetchImpl;
38
+ constructor(opts: SpeechRouterOptions);
39
+ /** Open a live transcription session over WebSocket. */
40
+ listen(opts: ListenOptions): ListenStream;
41
+ /** Transcribe a complete file. Returns `{ text }`. */
42
+ transcribe(opts: TranscribeOptions): Promise<Transcription>;
43
+ transcribe(opts: TranscribeOptions & {
44
+ responseFormat: 'verbose_json';
45
+ }): Promise<VerboseTranscription>;
46
+ transcribe(opts: TranscribeOptions & {
47
+ responseFormat: 'srt' | 'vtt' | 'text';
48
+ }): Promise<string>;
49
+ /** The live model catalog — slugs, capabilities, pricing. */
50
+ listModels(): Promise<Model[]>;
51
+ private toFormPart;
52
+ private partName;
53
+ private request;
54
+ }
55
+
56
+ export { type FileInput, ListenOptions, ListenStream, Model, SpeechRouter, type SpeechRouterOptions, type TranscribeOptions, Transcription, VerboseTranscription };