@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 +89 -4
- package/dist/index.cjs +441 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +401 -0
- package/dist/index.js.map +1 -0
- package/dist/listen-DEvmsIUS.d.cts +225 -0
- package/dist/listen-DEvmsIUS.d.ts +225 -0
- package/dist/mic.cjs +78 -0
- package/dist/mic.cjs.map +1 -0
- package/dist/mic.d.cts +23 -0
- package/dist/mic.d.ts +23 -0
- package/dist/mic.js +53 -0
- package/dist/mic.js.map +1 -0
- package/package.json +57 -11
- package/index.cjs +0 -1
- package/index.d.ts +0 -1
- package/index.js +0 -1
- package/mic.cjs +0 -1
- package/mic.d.ts +0 -1
- package/mic.js +0 -1
package/README.md
CHANGED
|
@@ -1,10 +1,95 @@
|
|
|
1
1
|
# @speechrouter/sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -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 };
|