@voiceinput/deepgram 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/index.cjs +427 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +425 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +167 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +36 -0
- package/dist/server.d.cts.map +1 -0
- package/dist/server.d.ts +36 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +166 -0
- package/dist/server.js.map +1 -0
- package/dist/session-config-JCvVU8gI.js +104 -0
- package/dist/session-config-JCvVU8gI.js.map +1 -0
- package/dist/session-config-aoO2eGMT.cjs +127 -0
- package/dist/session-config-aoO2eGMT.cjs.map +1 -0
- package/package.json +77 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { VoiceInputProviderV1 } from "@voiceinput/provider";
|
|
2
|
+
//#region src/session-config.d.ts
|
|
3
|
+
declare const DEEPGRAM_DEFAULT_MODEL = "nova-3";
|
|
4
|
+
interface DeepgramRealtimeSettings {
|
|
5
|
+
readonly smartFormat?: boolean;
|
|
6
|
+
readonly punctuate?: boolean;
|
|
7
|
+
readonly profanityFilter?: boolean;
|
|
8
|
+
readonly numerals?: boolean;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/index.d.ts
|
|
12
|
+
interface DeepgramVoiceInputProviderOptions extends DeepgramRealtimeSettings {
|
|
13
|
+
readonly tokenEndpoint: string | URL;
|
|
14
|
+
readonly model?: string;
|
|
15
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
16
|
+
readonly webSocket?: typeof globalThis.WebSocket;
|
|
17
|
+
readonly realtimeUrl?: string;
|
|
18
|
+
}
|
|
19
|
+
declare function deepgram(options: DeepgramVoiceInputProviderOptions): VoiceInputProviderV1;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { DEEPGRAM_DEFAULT_MODEL, DeepgramVoiceInputProviderOptions, deepgram };
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/session-config.ts","../src/index.ts"],"mappings":";;cAKa;UAYI;WACN;WACA;WACA;WACA;;;;UCAM,0CAA0C;WAChD,wBAAwB;WACxB;WACA,eAAe,WAAW;WAC1B,mBAAmB,WAAW;WAC9B;;iBAGK,SACd,SAAS,oCACR"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { i as validateDeepgramConfiguration, n as DEEPGRAM_SAMPLE_RATE, r as createDeepgramRealtimeUrl, t as DEEPGRAM_DEFAULT_MODEL } from "./session-config-JCvVU8gI.js";
|
|
2
|
+
import { sendWithBackpressure } from "@voiceinput/provider/transport";
|
|
3
|
+
import { VoiceInputError } from "@voiceinput/provider";
|
|
4
|
+
//#region src/index.ts
|
|
5
|
+
const DEFAULT_REALTIME_URL = "wss://api.deepgram.com/v1/listen";
|
|
6
|
+
function deepgram(options) {
|
|
7
|
+
const model = factoryString(options.model ?? "nova-3", "model");
|
|
8
|
+
const tokenEndpoint = factoryString(String(options.tokenEndpoint), "tokenEndpoint");
|
|
9
|
+
const realtimeUrl = factoryString(options.realtimeUrl ?? DEFAULT_REALTIME_URL, "realtimeUrl");
|
|
10
|
+
const providerSettings = {
|
|
11
|
+
...options.smartFormat === void 0 ? {} : { smartFormat: options.smartFormat },
|
|
12
|
+
...options.punctuate === void 0 ? {} : { punctuate: options.punctuate },
|
|
13
|
+
...options.profanityFilter === void 0 ? {} : { profanityFilter: options.profanityFilter },
|
|
14
|
+
...options.numerals === void 0 ? {} : { numerals: options.numerals }
|
|
15
|
+
};
|
|
16
|
+
const validateProviderOptions = (transcriptionOptions) => {
|
|
17
|
+
try {
|
|
18
|
+
validateDeepgramConfiguration({
|
|
19
|
+
model,
|
|
20
|
+
...providerSettings,
|
|
21
|
+
...transcriptionOptions
|
|
22
|
+
});
|
|
23
|
+
} catch (cause) {
|
|
24
|
+
if (VoiceInputError.isInstance(cause)) throw cause;
|
|
25
|
+
throw invalidConfiguration(cause);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
return Object.freeze({
|
|
29
|
+
specificationVersion: "v1",
|
|
30
|
+
provider: "deepgram",
|
|
31
|
+
modelId: model,
|
|
32
|
+
sampleRate: DEEPGRAM_SAMPLE_RATE,
|
|
33
|
+
validateOptions: validateProviderOptions,
|
|
34
|
+
async doOpen(callOptions) {
|
|
35
|
+
validateProviderOptions(callOptions);
|
|
36
|
+
const configuration = validateDeepgramConfiguration({
|
|
37
|
+
model,
|
|
38
|
+
...providerSettings,
|
|
39
|
+
...callOptions
|
|
40
|
+
});
|
|
41
|
+
return await openSession({
|
|
42
|
+
abortSignal: callOptions.abortSignal,
|
|
43
|
+
configuration,
|
|
44
|
+
fetchImplementation: options.fetch ?? globalThis.fetch,
|
|
45
|
+
realtimeUrl,
|
|
46
|
+
tokenEndpoint,
|
|
47
|
+
WebSocketImplementation: options.webSocket ?? globalThis.WebSocket
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function openSession(options) {
|
|
53
|
+
throwIfAborted(options.abortSignal);
|
|
54
|
+
requireBrowserFunction(options.fetchImplementation, "fetch");
|
|
55
|
+
requireBrowserFunction(options.WebSocketImplementation, "WebSocket");
|
|
56
|
+
const token = await requestToken(options.fetchImplementation, options.tokenEndpoint, options.configuration.model, options.abortSignal);
|
|
57
|
+
throwIfAborted(options.abortSignal);
|
|
58
|
+
return await createSession(new options.WebSocketImplementation(createDeepgramRealtimeUrl(options.realtimeUrl, options.configuration), ["bearer", token]), options.abortSignal);
|
|
59
|
+
}
|
|
60
|
+
async function requestToken(fetchImplementation, tokenEndpoint, model, abortSignal) {
|
|
61
|
+
let response;
|
|
62
|
+
try {
|
|
63
|
+
response = await fetchImplementation(tokenEndpoint, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: { "Content-Type": "application/json" },
|
|
66
|
+
body: JSON.stringify({ model }),
|
|
67
|
+
credentials: "same-origin",
|
|
68
|
+
signal: abortSignal
|
|
69
|
+
});
|
|
70
|
+
} catch (cause) {
|
|
71
|
+
throwIfAborted(abortSignal);
|
|
72
|
+
throw new VoiceInputError({
|
|
73
|
+
code: "network-error",
|
|
74
|
+
message: "Unable to reach the Deepgram token endpoint.",
|
|
75
|
+
provider: "deepgram",
|
|
76
|
+
retryable: true,
|
|
77
|
+
cause
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (!response.ok) throw await tokenResponseError(response);
|
|
81
|
+
let value;
|
|
82
|
+
try {
|
|
83
|
+
value = await response.json();
|
|
84
|
+
} catch (cause) {
|
|
85
|
+
throw new VoiceInputError({
|
|
86
|
+
code: "token-error",
|
|
87
|
+
message: "The Deepgram token endpoint returned invalid JSON.",
|
|
88
|
+
provider: "deepgram",
|
|
89
|
+
cause
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (!isRecord(value) || !nonEmpty(value["access_token"])) throw new VoiceInputError({
|
|
93
|
+
code: "token-error",
|
|
94
|
+
message: "The Deepgram token endpoint returned an invalid token.",
|
|
95
|
+
provider: "deepgram"
|
|
96
|
+
});
|
|
97
|
+
return value["access_token"];
|
|
98
|
+
}
|
|
99
|
+
async function createSession(socket, abortSignal) {
|
|
100
|
+
let controller;
|
|
101
|
+
const stream = new ReadableStream({ start(value) {
|
|
102
|
+
controller = value;
|
|
103
|
+
} });
|
|
104
|
+
if (controller === void 0) throw new VoiceInputError({
|
|
105
|
+
code: "provider-error",
|
|
106
|
+
message: "Unable to initialize the Deepgram transcript stream.",
|
|
107
|
+
provider: "deepgram"
|
|
108
|
+
});
|
|
109
|
+
let audioSent = false;
|
|
110
|
+
let closed = false;
|
|
111
|
+
let failed = false;
|
|
112
|
+
let finishing = false;
|
|
113
|
+
const closedSegments = /* @__PURE__ */ new Set();
|
|
114
|
+
let lastInterim = "";
|
|
115
|
+
let speechActive = false;
|
|
116
|
+
const closeSocket = (reason) => {
|
|
117
|
+
if (socket.readyState === 0 || socket.readyState === 1) socket.close(1e3, reason);
|
|
118
|
+
};
|
|
119
|
+
const closeStream = () => {
|
|
120
|
+
if (closed) return;
|
|
121
|
+
closed = true;
|
|
122
|
+
abortSignal.removeEventListener("abort", abort);
|
|
123
|
+
controller?.close();
|
|
124
|
+
};
|
|
125
|
+
const finishCleanly = () => {
|
|
126
|
+
closeStream();
|
|
127
|
+
closeSocket("finished");
|
|
128
|
+
};
|
|
129
|
+
const fail = (error) => {
|
|
130
|
+
if (closed || failed) return;
|
|
131
|
+
failed = true;
|
|
132
|
+
controller?.enqueue({
|
|
133
|
+
type: "error",
|
|
134
|
+
error
|
|
135
|
+
});
|
|
136
|
+
closeStream();
|
|
137
|
+
closeSocket("aborted");
|
|
138
|
+
};
|
|
139
|
+
const startSpeech = () => {
|
|
140
|
+
if (!speechActive) {
|
|
141
|
+
speechActive = true;
|
|
142
|
+
controller?.enqueue({ type: "speech-start" });
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const endSpeech = () => {
|
|
146
|
+
if (speechActive) {
|
|
147
|
+
speechActive = false;
|
|
148
|
+
controller?.enqueue({ type: "speech-end" });
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const handleResults = (value) => {
|
|
152
|
+
const channel = value["channel"];
|
|
153
|
+
if (!isRecord(channel) || !Array.isArray(channel["alternatives"])) throw new TypeError("Deepgram Results did not contain alternatives.");
|
|
154
|
+
const alternative = channel["alternatives"][0];
|
|
155
|
+
if (!isRecord(alternative) || typeof alternative["transcript"] !== "string") throw new TypeError("Deepgram Results did not contain a transcript.");
|
|
156
|
+
const text = alternative["transcript"];
|
|
157
|
+
const isFinal = value["is_final"] === true;
|
|
158
|
+
const start = value["start"];
|
|
159
|
+
if (typeof start !== "number" || !Number.isFinite(start) || start < 0) throw new TypeError("Deepgram Results did not contain an audio start boundary.");
|
|
160
|
+
const segmentId = `audio:${start}`;
|
|
161
|
+
if (closedSegments.has(segmentId)) return;
|
|
162
|
+
if (text.length > 0 || isFinal) {
|
|
163
|
+
if (text.length > 0) startSpeech();
|
|
164
|
+
if (isFinal) {
|
|
165
|
+
closedSegments.add(segmentId);
|
|
166
|
+
controller?.enqueue({
|
|
167
|
+
type: "final",
|
|
168
|
+
text,
|
|
169
|
+
segmentId
|
|
170
|
+
});
|
|
171
|
+
lastInterim = "";
|
|
172
|
+
} else if (text !== lastInterim) {
|
|
173
|
+
lastInterim = text;
|
|
174
|
+
controller?.enqueue({
|
|
175
|
+
type: "interim",
|
|
176
|
+
text,
|
|
177
|
+
segmentId
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (value["speech_final"] === true) endSpeech();
|
|
182
|
+
};
|
|
183
|
+
const handleMessage = (event) => {
|
|
184
|
+
if (closed) return;
|
|
185
|
+
try {
|
|
186
|
+
const value = JSON.parse(String(event.data));
|
|
187
|
+
if (!isRecord(value) || !nonEmpty(value["type"])) throw new TypeError("Deepgram sent an invalid streaming event.");
|
|
188
|
+
const type = value["type"];
|
|
189
|
+
if (type === "Results") handleResults(value);
|
|
190
|
+
else if (type === "SpeechStarted") startSpeech();
|
|
191
|
+
else if (type === "UtteranceEnd") endSpeech();
|
|
192
|
+
else if (type === "Error") fail(normalizeMessageError(value));
|
|
193
|
+
} catch (cause) {
|
|
194
|
+
fail(new VoiceInputError({
|
|
195
|
+
code: "provider-error",
|
|
196
|
+
message: "Deepgram sent an invalid streaming event.",
|
|
197
|
+
provider: "deepgram",
|
|
198
|
+
cause
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const handleClose = (event) => {
|
|
203
|
+
if (closed) return;
|
|
204
|
+
if (event.code === 1e3) closeStream();
|
|
205
|
+
else fail(normalizeCloseError(event));
|
|
206
|
+
};
|
|
207
|
+
function abort() {
|
|
208
|
+
if (closed) return;
|
|
209
|
+
closeStream();
|
|
210
|
+
closeSocket("aborted");
|
|
211
|
+
}
|
|
212
|
+
socket.addEventListener("message", handleMessage);
|
|
213
|
+
socket.addEventListener("close", handleClose);
|
|
214
|
+
socket.addEventListener("error", () => {
|
|
215
|
+
fail(new VoiceInputError({
|
|
216
|
+
code: "network-error",
|
|
217
|
+
message: "The Deepgram streaming connection failed.",
|
|
218
|
+
provider: "deepgram",
|
|
219
|
+
retryable: true
|
|
220
|
+
}));
|
|
221
|
+
});
|
|
222
|
+
abortSignal.addEventListener("abort", abort, { once: true });
|
|
223
|
+
await waitForOpen(socket, abortSignal);
|
|
224
|
+
return {
|
|
225
|
+
stream,
|
|
226
|
+
sendAudio(chunk) {
|
|
227
|
+
if (closed || finishing || chunk.length === 0) return;
|
|
228
|
+
audioSent = true;
|
|
229
|
+
try {
|
|
230
|
+
return sendWithBackpressure(socket, chunk.byteLength, abortSignal, "deepgram", () => socket.send(new Int16Array(chunk).buffer));
|
|
231
|
+
} catch (cause) {
|
|
232
|
+
throw new VoiceInputError({
|
|
233
|
+
code: "network-error",
|
|
234
|
+
message: "Unable to send audio to Deepgram.",
|
|
235
|
+
provider: "deepgram",
|
|
236
|
+
retryable: true,
|
|
237
|
+
cause
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
finish() {
|
|
242
|
+
if (closed || finishing) return;
|
|
243
|
+
finishing = true;
|
|
244
|
+
if (!audioSent) {
|
|
245
|
+
finishCleanly();
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
sendJson(socket, { type: "CloseStream" });
|
|
249
|
+
},
|
|
250
|
+
abort
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
function normalizeMessageError(value) {
|
|
254
|
+
const code = typeof value["code"] === "string" ? value["code"] : "";
|
|
255
|
+
const description = typeof value["description"] === "string" ? value["description"] : typeof value["message"] === "string" ? value["message"] : "";
|
|
256
|
+
const source = `${code} ${description}`;
|
|
257
|
+
const rateLimited = /rate|quota|429/iu.test(source);
|
|
258
|
+
const unauthorized = /auth|unauthorized|401|403/iu.test(source);
|
|
259
|
+
return new VoiceInputError({
|
|
260
|
+
code: unauthorized ? "unauthorized" : rateLimited ? "rate-limited" : "provider-error",
|
|
261
|
+
message: description || "Deepgram reported a streaming error.",
|
|
262
|
+
provider: "deepgram",
|
|
263
|
+
retryable: rateLimited || /internal|unavailable/iu.test(source),
|
|
264
|
+
cause: value
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function normalizeCloseError(event) {
|
|
268
|
+
const reason = event.reason ?? "";
|
|
269
|
+
const rateLimited = event.code === 1013 || /rate|quota|429/iu.test(reason);
|
|
270
|
+
const unauthorized = /auth|unauthorized|401|403/iu.test(reason);
|
|
271
|
+
const invalidAudio = event.code === 1008 && /data|audio|decode/iu.test(reason);
|
|
272
|
+
return new VoiceInputError({
|
|
273
|
+
code: unauthorized ? "unauthorized" : rateLimited ? "rate-limited" : invalidAudio ? "audio-error" : "network-error",
|
|
274
|
+
message: "The Deepgram streaming connection closed unexpectedly.",
|
|
275
|
+
provider: "deepgram",
|
|
276
|
+
retryable: rateLimited || !unauthorized && !invalidAudio,
|
|
277
|
+
cause: event
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function invalidConfiguration(cause) {
|
|
281
|
+
return new VoiceInputError({
|
|
282
|
+
code: "invalid-configuration",
|
|
283
|
+
message: cause instanceof Error ? cause.message : "Invalid Deepgram transcription options.",
|
|
284
|
+
provider: "deepgram",
|
|
285
|
+
cause
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function requireBrowserFunction(value, feature) {
|
|
289
|
+
if (typeof value !== "function") throw new VoiceInputError({
|
|
290
|
+
code: "unsupported-browser",
|
|
291
|
+
message: `Deepgram voice input requires browser ${feature} support.`,
|
|
292
|
+
provider: "deepgram"
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
function factoryString(value, name) {
|
|
296
|
+
if (value.trim().length === 0) throw invalidConfiguration(/* @__PURE__ */ new TypeError(`${name} must be non-empty.`));
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
async function tokenResponseError(response) {
|
|
300
|
+
const retryAfterMs = parseRetryAfter(response.headers.get("Retry-After"));
|
|
301
|
+
if (response.status === 401 || response.status === 403) return new VoiceInputError({
|
|
302
|
+
code: "unauthorized",
|
|
303
|
+
message: "The Deepgram token endpoint rejected this request.",
|
|
304
|
+
provider: "deepgram"
|
|
305
|
+
});
|
|
306
|
+
if (response.status === 429) return new VoiceInputError({
|
|
307
|
+
code: "rate-limited",
|
|
308
|
+
message: "The Deepgram token endpoint rate limit was exceeded.",
|
|
309
|
+
provider: "deepgram",
|
|
310
|
+
retryable: true,
|
|
311
|
+
...retryAfterMs === void 0 ? {} : { retryAfterMs }
|
|
312
|
+
});
|
|
313
|
+
const safeError = await readSafeTokenError(response);
|
|
314
|
+
if (safeError !== void 0) return new VoiceInputError({
|
|
315
|
+
...safeError,
|
|
316
|
+
provider: "deepgram"
|
|
317
|
+
});
|
|
318
|
+
return new VoiceInputError({
|
|
319
|
+
code: "token-error",
|
|
320
|
+
message: "The Deepgram token endpoint did not issue a token.",
|
|
321
|
+
provider: "deepgram",
|
|
322
|
+
retryable: response.status >= 500
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
async function readSafeTokenError(response) {
|
|
326
|
+
if (response.status !== 400 || response.headers.get("X-VoiceInput-Error") !== "1" || response.headers.get("Content-Type")?.split(";", 1)[0]?.trim() !== "application/json") return;
|
|
327
|
+
const text = await readBoundedErrorText(response);
|
|
328
|
+
if (text === void 0) return void 0;
|
|
329
|
+
try {
|
|
330
|
+
const value = JSON.parse(text);
|
|
331
|
+
const error = isRecord(value) ? value["error"] : void 0;
|
|
332
|
+
if (!isRecord(error) || error["code"] !== "invalid-configuration" && error["code"] !== "unsupported-feature" || typeof error["message"] !== "string" || error["message"].length === 0 || error["message"].length > 1e3) return;
|
|
333
|
+
return {
|
|
334
|
+
code: error["code"],
|
|
335
|
+
message: error["message"]
|
|
336
|
+
};
|
|
337
|
+
} catch {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async function readBoundedErrorText(response) {
|
|
342
|
+
const reader = response.body?.getReader();
|
|
343
|
+
if (reader === void 0) return void 0;
|
|
344
|
+
const decoder = new TextDecoder();
|
|
345
|
+
let bytesRead = 0;
|
|
346
|
+
let text = "";
|
|
347
|
+
try {
|
|
348
|
+
while (true) {
|
|
349
|
+
const { done, value } = await reader.read();
|
|
350
|
+
if (done) return text + decoder.decode();
|
|
351
|
+
bytesRead += value.byteLength;
|
|
352
|
+
if (bytesRead > 4096) {
|
|
353
|
+
await reader.cancel().catch(() => {});
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
text += decoder.decode(value, { stream: true });
|
|
357
|
+
}
|
|
358
|
+
} catch {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function waitForOpen(socket, abortSignal) {
|
|
363
|
+
if (socket.readyState === 1) return Promise.resolve();
|
|
364
|
+
return new Promise((resolve, reject) => {
|
|
365
|
+
const cleanup = () => {
|
|
366
|
+
socket.removeEventListener("open", handleOpen);
|
|
367
|
+
socket.removeEventListener("error", handleError);
|
|
368
|
+
abortSignal.removeEventListener("abort", handleAbort);
|
|
369
|
+
};
|
|
370
|
+
const handleOpen = () => {
|
|
371
|
+
cleanup();
|
|
372
|
+
resolve();
|
|
373
|
+
};
|
|
374
|
+
const handleError = (event) => {
|
|
375
|
+
cleanup();
|
|
376
|
+
reject(new VoiceInputError({
|
|
377
|
+
code: "network-error",
|
|
378
|
+
message: "Unable to open the Deepgram streaming connection.",
|
|
379
|
+
provider: "deepgram",
|
|
380
|
+
retryable: true,
|
|
381
|
+
cause: event
|
|
382
|
+
}));
|
|
383
|
+
};
|
|
384
|
+
const handleAbort = () => {
|
|
385
|
+
cleanup();
|
|
386
|
+
reject(abortSignal.reason);
|
|
387
|
+
};
|
|
388
|
+
socket.addEventListener("open", handleOpen, { once: true });
|
|
389
|
+
socket.addEventListener("error", handleError, { once: true });
|
|
390
|
+
abortSignal.addEventListener("abort", handleAbort, { once: true });
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
function sendJson(socket, value) {
|
|
394
|
+
try {
|
|
395
|
+
socket.send(JSON.stringify(value));
|
|
396
|
+
} catch (cause) {
|
|
397
|
+
throw new VoiceInputError({
|
|
398
|
+
code: "network-error",
|
|
399
|
+
message: "Unable to send data to Deepgram.",
|
|
400
|
+
provider: "deepgram",
|
|
401
|
+
retryable: true,
|
|
402
|
+
cause
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
function parseRetryAfter(value) {
|
|
407
|
+
if (value === null) return;
|
|
408
|
+
const seconds = Number(value);
|
|
409
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.ceil(seconds * 1e3);
|
|
410
|
+
const date = Date.parse(value);
|
|
411
|
+
return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
|
|
412
|
+
}
|
|
413
|
+
function throwIfAborted(signal) {
|
|
414
|
+
if (signal.aborted) throw signal.reason;
|
|
415
|
+
}
|
|
416
|
+
function nonEmpty(value) {
|
|
417
|
+
return typeof value === "string" && value.length > 0;
|
|
418
|
+
}
|
|
419
|
+
function isRecord(value) {
|
|
420
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
421
|
+
}
|
|
422
|
+
//#endregion
|
|
423
|
+
export { DEEPGRAM_DEFAULT_MODEL, deepgram };
|
|
424
|
+
|
|
425
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { sendWithBackpressure } from \"@voiceinput/provider/transport\";\nimport {\n VoiceInputError,\n type VoiceInputProviderV1,\n type VoiceInputProviderV1CallOptions,\n type VoiceInputProviderV1Session,\n type VoiceInputProviderV1StreamPart,\n type VoiceTranscriptionOptions,\n} from \"@voiceinput/provider\";\n\nimport {\n DEEPGRAM_DEFAULT_MODEL,\n DEEPGRAM_SAMPLE_RATE,\n createDeepgramRealtimeUrl,\n validateDeepgramConfiguration,\n type DeepgramRealtimeSettings,\n type DeepgramSessionConfiguration,\n} from \"./session-config.js\";\n\nconst DEFAULT_REALTIME_URL = \"wss://api.deepgram.com/v1/listen\";\n\nexport interface DeepgramVoiceInputProviderOptions extends DeepgramRealtimeSettings {\n readonly tokenEndpoint: string | URL;\n readonly model?: string;\n readonly fetch?: typeof globalThis.fetch;\n readonly webSocket?: typeof globalThis.WebSocket;\n readonly realtimeUrl?: string;\n}\n\nexport function deepgram(\n options: DeepgramVoiceInputProviderOptions,\n): VoiceInputProviderV1 {\n const model = factoryString(options.model ?? DEEPGRAM_DEFAULT_MODEL, \"model\");\n const tokenEndpoint = factoryString(\n String(options.tokenEndpoint),\n \"tokenEndpoint\",\n );\n const realtimeUrl = factoryString(\n options.realtimeUrl ?? DEFAULT_REALTIME_URL,\n \"realtimeUrl\",\n );\n const providerSettings: DeepgramRealtimeSettings = {\n ...(options.smartFormat === undefined\n ? {}\n : { smartFormat: options.smartFormat }),\n ...(options.punctuate === undefined\n ? {}\n : { punctuate: options.punctuate }),\n ...(options.profanityFilter === undefined\n ? {}\n : { profanityFilter: options.profanityFilter }),\n ...(options.numerals === undefined ? {} : { numerals: options.numerals }),\n };\n const validateProviderOptions = (\n transcriptionOptions: VoiceTranscriptionOptions,\n ): void => {\n try {\n validateDeepgramConfiguration({\n model,\n ...providerSettings,\n ...transcriptionOptions,\n });\n } catch (cause) {\n if (VoiceInputError.isInstance(cause)) {\n throw cause;\n }\n throw invalidConfiguration(cause);\n }\n };\n\n return Object.freeze({\n specificationVersion: \"v1\" as const,\n provider: \"deepgram\",\n modelId: model,\n sampleRate: DEEPGRAM_SAMPLE_RATE,\n validateOptions: validateProviderOptions,\n async doOpen(callOptions: VoiceInputProviderV1CallOptions) {\n validateProviderOptions(callOptions);\n const configuration = validateDeepgramConfiguration({\n model,\n ...providerSettings,\n ...callOptions,\n });\n return await openSession({\n abortSignal: callOptions.abortSignal,\n configuration,\n fetchImplementation: options.fetch ?? globalThis.fetch,\n realtimeUrl,\n tokenEndpoint,\n WebSocketImplementation: options.webSocket ?? globalThis.WebSocket,\n });\n },\n });\n}\n\nasync function openSession(options: {\n abortSignal: AbortSignal;\n configuration: DeepgramSessionConfiguration;\n fetchImplementation: typeof globalThis.fetch;\n realtimeUrl: string;\n tokenEndpoint: string;\n WebSocketImplementation: typeof globalThis.WebSocket;\n}): Promise<VoiceInputProviderV1Session> {\n throwIfAborted(options.abortSignal);\n requireBrowserFunction(options.fetchImplementation, \"fetch\");\n requireBrowserFunction(options.WebSocketImplementation, \"WebSocket\");\n const token = await requestToken(\n options.fetchImplementation,\n options.tokenEndpoint,\n options.configuration.model,\n options.abortSignal,\n );\n throwIfAborted(options.abortSignal);\n const socket = new options.WebSocketImplementation(\n createDeepgramRealtimeUrl(options.realtimeUrl, options.configuration),\n [\"bearer\", token],\n );\n return await createSession(socket, options.abortSignal);\n}\n\nasync function requestToken(\n fetchImplementation: typeof globalThis.fetch,\n tokenEndpoint: string,\n model: string,\n abortSignal: AbortSignal,\n): Promise<string> {\n let response: Response;\n try {\n response = await fetchImplementation(tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ model }),\n credentials: \"same-origin\",\n signal: abortSignal,\n });\n } catch (cause) {\n throwIfAborted(abortSignal);\n throw new VoiceInputError({\n code: \"network-error\",\n message: \"Unable to reach the Deepgram token endpoint.\",\n provider: \"deepgram\",\n retryable: true,\n cause,\n });\n }\n if (!response.ok) {\n throw await tokenResponseError(response);\n }\n let value: unknown;\n try {\n value = await response.json();\n } catch (cause) {\n throw new VoiceInputError({\n code: \"token-error\",\n message: \"The Deepgram token endpoint returned invalid JSON.\",\n provider: \"deepgram\",\n cause,\n });\n }\n if (!isRecord(value) || !nonEmpty(value[\"access_token\"])) {\n throw new VoiceInputError({\n code: \"token-error\",\n message: \"The Deepgram token endpoint returned an invalid token.\",\n provider: \"deepgram\",\n });\n }\n return value[\"access_token\"];\n}\n\nasync function createSession(\n socket: WebSocket,\n abortSignal: AbortSignal,\n): Promise<VoiceInputProviderV1Session> {\n let controller:\n ReadableStreamDefaultController<VoiceInputProviderV1StreamPart> | undefined;\n const stream = new ReadableStream<VoiceInputProviderV1StreamPart>({\n start(value) {\n controller = value;\n },\n });\n if (controller === undefined) {\n throw new VoiceInputError({\n code: \"provider-error\",\n message: \"Unable to initialize the Deepgram transcript stream.\",\n provider: \"deepgram\",\n });\n }\n\n let audioSent = false;\n let closed = false;\n let failed = false;\n let finishing = false;\n const closedSegments = new Set<string>();\n let lastInterim = \"\";\n let speechActive = false;\n\n const closeSocket = (reason: \"aborted\" | \"finished\"): void => {\n if (socket.readyState === 0 || socket.readyState === 1) {\n socket.close(1000, reason);\n }\n };\n const closeStream = (): void => {\n if (closed) {\n return;\n }\n closed = true;\n abortSignal.removeEventListener(\"abort\", abort);\n controller?.close();\n };\n const finishCleanly = (): void => {\n closeStream();\n closeSocket(\"finished\");\n };\n const fail = (error: VoiceInputError): void => {\n if (closed || failed) {\n return;\n }\n failed = true;\n controller?.enqueue({ type: \"error\", error });\n closeStream();\n closeSocket(\"aborted\");\n };\n const startSpeech = (): void => {\n if (!speechActive) {\n speechActive = true;\n controller?.enqueue({ type: \"speech-start\" });\n }\n };\n const endSpeech = (): void => {\n if (speechActive) {\n speechActive = false;\n controller?.enqueue({ type: \"speech-end\" });\n }\n };\n const handleResults = (value: Record<string, unknown>): void => {\n const channel = value[\"channel\"];\n if (!isRecord(channel) || !Array.isArray(channel[\"alternatives\"])) {\n throw new TypeError(\"Deepgram Results did not contain alternatives.\");\n }\n const alternative = channel[\"alternatives\"][0];\n if (\n !isRecord(alternative) ||\n typeof alternative[\"transcript\"] !== \"string\"\n ) {\n throw new TypeError(\"Deepgram Results did not contain a transcript.\");\n }\n const text = alternative[\"transcript\"];\n const isFinal = value[\"is_final\"] === true;\n const start = value[\"start\"];\n if (typeof start !== \"number\" || !Number.isFinite(start) || start < 0)\n throw new TypeError(\n \"Deepgram Results did not contain an audio start boundary.\",\n );\n const segmentId = `audio:${start}`;\n if (closedSegments.has(segmentId)) return;\n if (text.length > 0 || isFinal) {\n if (text.length > 0) startSpeech();\n if (isFinal) {\n closedSegments.add(segmentId);\n controller?.enqueue({ type: \"final\", text, segmentId });\n lastInterim = \"\";\n } else if (text !== lastInterim) {\n lastInterim = text;\n controller?.enqueue({ type: \"interim\", text, segmentId });\n }\n }\n if (value[\"speech_final\"] === true) {\n endSpeech();\n }\n };\n const handleMessage = (event: MessageEvent): void => {\n if (closed) {\n return;\n }\n try {\n const value = JSON.parse(String(event.data)) as unknown;\n if (!isRecord(value) || !nonEmpty(value[\"type\"])) {\n throw new TypeError(\"Deepgram sent an invalid streaming event.\");\n }\n const type = value[\"type\"];\n if (type === \"Results\") {\n handleResults(value);\n } else if (type === \"SpeechStarted\") {\n startSpeech();\n } else if (type === \"UtteranceEnd\") {\n endSpeech();\n } else if (type === \"Error\") {\n fail(normalizeMessageError(value));\n }\n } catch (cause) {\n fail(\n new VoiceInputError({\n code: \"provider-error\",\n message: \"Deepgram sent an invalid streaming event.\",\n provider: \"deepgram\",\n cause,\n }),\n );\n }\n };\n const handleClose = (event: CloseEvent): void => {\n if (closed) {\n return;\n }\n if (event.code === 1000) {\n closeStream();\n } else {\n fail(normalizeCloseError(event));\n }\n };\n function abort(): void {\n if (closed) {\n return;\n }\n closeStream();\n closeSocket(\"aborted\");\n }\n\n socket.addEventListener(\"message\", handleMessage);\n socket.addEventListener(\"close\", handleClose);\n socket.addEventListener(\"error\", () => {\n fail(\n new VoiceInputError({\n code: \"network-error\",\n message: \"The Deepgram streaming connection failed.\",\n provider: \"deepgram\",\n retryable: true,\n }),\n );\n });\n abortSignal.addEventListener(\"abort\", abort, { once: true });\n await waitForOpen(socket, abortSignal);\n\n return {\n stream,\n sendAudio(chunk) {\n if (closed || finishing || chunk.length === 0) {\n return;\n }\n audioSent = true;\n try {\n return sendWithBackpressure(\n socket,\n chunk.byteLength,\n abortSignal,\n \"deepgram\",\n () => socket.send(new Int16Array(chunk).buffer),\n );\n } catch (cause) {\n throw new VoiceInputError({\n code: \"network-error\",\n message: \"Unable to send audio to Deepgram.\",\n provider: \"deepgram\",\n retryable: true,\n cause,\n });\n }\n },\n finish() {\n if (closed || finishing) {\n return;\n }\n finishing = true;\n if (!audioSent) {\n finishCleanly();\n return;\n }\n sendJson(socket, { type: \"CloseStream\" });\n },\n abort,\n };\n}\n\nfunction normalizeMessageError(\n value: Record<string, unknown>,\n): VoiceInputError {\n const code = typeof value[\"code\"] === \"string\" ? value[\"code\"] : \"\";\n const description =\n typeof value[\"description\"] === \"string\"\n ? value[\"description\"]\n : typeof value[\"message\"] === \"string\"\n ? value[\"message\"]\n : \"\";\n const source = `${code} ${description}`;\n const rateLimited = /rate|quota|429/iu.test(source);\n const unauthorized = /auth|unauthorized|401|403/iu.test(source);\n return new VoiceInputError({\n code: unauthorized\n ? \"unauthorized\"\n : rateLimited\n ? \"rate-limited\"\n : \"provider-error\",\n message: description || \"Deepgram reported a streaming error.\",\n provider: \"deepgram\",\n retryable: rateLimited || /internal|unavailable/iu.test(source),\n cause: value,\n });\n}\n\nfunction normalizeCloseError(event: CloseEvent): VoiceInputError {\n const reason = event.reason ?? \"\";\n const rateLimited = event.code === 1013 || /rate|quota|429/iu.test(reason);\n const unauthorized = /auth|unauthorized|401|403/iu.test(reason);\n const invalidAudio =\n event.code === 1008 && /data|audio|decode/iu.test(reason);\n return new VoiceInputError({\n code: unauthorized\n ? \"unauthorized\"\n : rateLimited\n ? \"rate-limited\"\n : invalidAudio\n ? \"audio-error\"\n : \"network-error\",\n message: \"The Deepgram streaming connection closed unexpectedly.\",\n provider: \"deepgram\",\n retryable: rateLimited || (!unauthorized && !invalidAudio),\n cause: event,\n });\n}\n\nfunction invalidConfiguration(cause: unknown): VoiceInputError {\n return new VoiceInputError({\n code: \"invalid-configuration\",\n message:\n cause instanceof Error\n ? cause.message\n : \"Invalid Deepgram transcription options.\",\n provider: \"deepgram\",\n cause,\n });\n}\n\nfunction requireBrowserFunction(value: unknown, feature: string): void {\n if (typeof value !== \"function\") {\n throw new VoiceInputError({\n code: \"unsupported-browser\",\n message: `Deepgram voice input requires browser ${feature} support.`,\n provider: \"deepgram\",\n });\n }\n}\n\nfunction factoryString(value: string, name: string): string {\n if (value.trim().length === 0) {\n throw invalidConfiguration(new TypeError(`${name} must be non-empty.`));\n }\n return value;\n}\n\nasync function tokenResponseError(\n response: Response,\n): Promise<VoiceInputError> {\n const retryAfterMs = parseRetryAfter(response.headers.get(\"Retry-After\"));\n if (response.status === 401 || response.status === 403) {\n return new VoiceInputError({\n code: \"unauthorized\",\n message: \"The Deepgram token endpoint rejected this request.\",\n provider: \"deepgram\",\n });\n }\n if (response.status === 429) {\n return new VoiceInputError({\n code: \"rate-limited\",\n message: \"The Deepgram token endpoint rate limit was exceeded.\",\n provider: \"deepgram\",\n retryable: true,\n ...(retryAfterMs === undefined ? {} : { retryAfterMs }),\n });\n }\n const safeError = await readSafeTokenError(response);\n if (safeError !== undefined) {\n return new VoiceInputError({ ...safeError, provider: \"deepgram\" });\n }\n return new VoiceInputError({\n code: \"token-error\",\n message: \"The Deepgram token endpoint did not issue a token.\",\n provider: \"deepgram\",\n retryable: response.status >= 500,\n });\n}\n\nasync function readSafeTokenError(response: Response): Promise<\n | {\n code: \"invalid-configuration\" | \"unsupported-feature\";\n message: string;\n }\n | undefined\n> {\n if (\n response.status !== 400 ||\n response.headers.get(\"X-VoiceInput-Error\") !== \"1\" ||\n response.headers.get(\"Content-Type\")?.split(\";\", 1)[0]?.trim() !==\n \"application/json\"\n ) {\n return undefined;\n }\n const text = await readBoundedErrorText(response);\n if (text === undefined) return undefined;\n try {\n const value = JSON.parse(text) as unknown;\n const error = isRecord(value) ? value[\"error\"] : undefined;\n if (\n !isRecord(error) ||\n (error[\"code\"] !== \"invalid-configuration\" &&\n error[\"code\"] !== \"unsupported-feature\") ||\n typeof error[\"message\"] !== \"string\" ||\n error[\"message\"].length === 0 ||\n error[\"message\"].length > 1_000\n ) {\n return undefined;\n }\n return { code: error[\"code\"], message: error[\"message\"] };\n } catch {\n return undefined;\n }\n}\n\nasync function readBoundedErrorText(\n response: Response,\n): Promise<string | undefined> {\n const reader = response.body?.getReader();\n if (reader === undefined) return undefined;\n const decoder = new TextDecoder();\n let bytesRead = 0;\n let text = \"\";\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) return text + decoder.decode();\n bytesRead += value.byteLength;\n if (bytesRead > 4_096) {\n await reader.cancel().catch(() => {});\n return undefined;\n }\n text += decoder.decode(value, { stream: true });\n }\n } catch {\n return undefined;\n }\n}\n\nfunction waitForOpen(\n socket: WebSocket,\n abortSignal: AbortSignal,\n): Promise<void> {\n if (socket.readyState === 1) {\n return Promise.resolve();\n }\n return new Promise((resolve, reject) => {\n const cleanup = (): void => {\n socket.removeEventListener(\"open\", handleOpen);\n socket.removeEventListener(\"error\", handleError);\n abortSignal.removeEventListener(\"abort\", handleAbort);\n };\n const handleOpen = (): void => {\n cleanup();\n resolve();\n };\n const handleError = (event: Event): void => {\n cleanup();\n reject(\n new VoiceInputError({\n code: \"network-error\",\n message: \"Unable to open the Deepgram streaming connection.\",\n provider: \"deepgram\",\n retryable: true,\n cause: event,\n }),\n );\n };\n const handleAbort = (): void => {\n cleanup();\n reject(abortSignal.reason);\n };\n socket.addEventListener(\"open\", handleOpen, { once: true });\n socket.addEventListener(\"error\", handleError, { once: true });\n abortSignal.addEventListener(\"abort\", handleAbort, { once: true });\n });\n}\n\nfunction sendJson(socket: WebSocket, value: Record<string, unknown>): void {\n try {\n socket.send(JSON.stringify(value));\n } catch (cause) {\n throw new VoiceInputError({\n code: \"network-error\",\n message: \"Unable to send data to Deepgram.\",\n provider: \"deepgram\",\n retryable: true,\n cause,\n });\n }\n}\n\nfunction parseRetryAfter(value: string | null): number | undefined {\n if (value === null) {\n return undefined;\n }\n const seconds = Number(value);\n if (Number.isFinite(seconds) && seconds >= 0) {\n return Math.ceil(seconds * 1_000);\n }\n const date = Date.parse(value);\n return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());\n}\n\nfunction throwIfAborted(signal: AbortSignal): void {\n if (signal.aborted) {\n throw signal.reason;\n }\n}\n\nfunction nonEmpty(value: unknown): value is string {\n return typeof value === \"string\" && value.length > 0;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport { DEEPGRAM_DEFAULT_MODEL } from \"./session-config.js\";\n"],"mappings":"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"}
|
package/dist/server.cjs
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
require("./session-config-aoO2eGMT.cjs");
|
|
3
|
+
//#region src/server.ts
|
|
4
|
+
const DEFAULT_GRANT_URL = "https://api.deepgram.com/v1/auth/grant";
|
|
5
|
+
const DEFAULT_TTL_SECONDS = 30;
|
|
6
|
+
const MAX_TOKEN_REQUEST_BYTES = 16384;
|
|
7
|
+
function createDeepgramTokenHandler(options) {
|
|
8
|
+
const defaultModel = nonEmpty(options.model ?? "nova-3", "model");
|
|
9
|
+
const allowedModels = new Set(options.allowedModels ?? [defaultModel]);
|
|
10
|
+
if (!allowedModels.has(defaultModel)) throw new TypeError("allowedModels must contain the default model.");
|
|
11
|
+
if (typeof options.authorize !== "function") throw new TypeError("authorize must be a function.");
|
|
12
|
+
const apiKey = nonEmpty(options.apiKey, "apiKey");
|
|
13
|
+
const ttlSeconds = validateTtl(options.ttlSeconds ?? DEFAULT_TTL_SECONDS);
|
|
14
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
15
|
+
const grantUrl = options.grantUrl ?? DEFAULT_GRANT_URL;
|
|
16
|
+
return async (request) => {
|
|
17
|
+
if (request.method !== "POST") return jsonError(405, "invalid-request", "Method not allowed.", { Allow: "POST" });
|
|
18
|
+
try {
|
|
19
|
+
const requestBody = await readJsonBody(request);
|
|
20
|
+
const authorization = await options.authorize(copyRequest(request, requestBody));
|
|
21
|
+
if (authorization === null) return jsonError(401, "unauthorized", "Unauthorized.");
|
|
22
|
+
const subject = nonEmpty(authorization.subject, "subject");
|
|
23
|
+
const model = readModel(requestBody, defaultModel);
|
|
24
|
+
if (!allowedModels.has(model)) return jsonError(400, "invalid-configuration", "The requested Deepgram model is not allowed.");
|
|
25
|
+
const context = {
|
|
26
|
+
request: copyRequest(request, requestBody),
|
|
27
|
+
subject,
|
|
28
|
+
model
|
|
29
|
+
};
|
|
30
|
+
const limit = await options.rateLimit?.(context);
|
|
31
|
+
if (limit?.allowed === false) {
|
|
32
|
+
const retryAfter = normalizeRetryAfter(limit.retryAfterSeconds);
|
|
33
|
+
return jsonError(429, "rate-limited", "Rate limit exceeded.", retryAfter === void 0 ? {} : { "Retry-After": String(retryAfter) });
|
|
34
|
+
}
|
|
35
|
+
const response = await fetchImplementation(grantUrl, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: {
|
|
38
|
+
Authorization: `Token ${apiKey}`,
|
|
39
|
+
"Content-Type": "application/json"
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({ ttl_seconds: ttlSeconds }),
|
|
42
|
+
signal: request.signal
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
if (response.status === 429) return jsonError(429, "rate-limited", "Deepgram rate limit exceeded.", copyRetryAfter(response.headers));
|
|
46
|
+
return jsonError(502, "token-error", "Deepgram did not issue a temporary token.");
|
|
47
|
+
}
|
|
48
|
+
const token = validateToken(await response.json());
|
|
49
|
+
await options.onTokenIssued?.({
|
|
50
|
+
provider: "deepgram",
|
|
51
|
+
subject,
|
|
52
|
+
model,
|
|
53
|
+
expiresIn: token.expires_in
|
|
54
|
+
});
|
|
55
|
+
return Response.json(token, { headers: { "Cache-Control": "no-store" } });
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (request.signal.aborted) return jsonError(499, "network-error", "The request was cancelled.");
|
|
58
|
+
if (error instanceof InvalidTokenRequestError) return jsonError(error.status, error.code, error.message);
|
|
59
|
+
return jsonError(500, "token-error", "Unable to issue a Deepgram temporary token.");
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function readModel(body, defaultModel) {
|
|
64
|
+
try {
|
|
65
|
+
if (body.length === 0) return defaultModel;
|
|
66
|
+
const value = JSON.parse(body);
|
|
67
|
+
if (!isRecord(value) || Object.keys(value).some((key) => key !== "model")) throw new TypeError("The token request must contain only model.");
|
|
68
|
+
return value["model"] === void 0 ? defaultModel : nonEmpty(value["model"], "model");
|
|
69
|
+
} catch (cause) {
|
|
70
|
+
if (cause instanceof SyntaxError || cause instanceof TypeError) throw new InvalidTokenRequestError(cause.message);
|
|
71
|
+
throw cause;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function copyRequest(request, body) {
|
|
75
|
+
return new Request(request, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
body,
|
|
78
|
+
referrer: request.referrer,
|
|
79
|
+
referrerPolicy: request.referrerPolicy
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function validateTtl(value) {
|
|
83
|
+
if (!Number.isInteger(value) || value < 1 || value > 3600) throw new TypeError("ttlSeconds must be an integer from 1 to 3600.");
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
function validateToken(value) {
|
|
87
|
+
if (!isRecord(value) || typeof value["access_token"] !== "string" || value["access_token"].length === 0 || typeof value["expires_in"] !== "number" || !Number.isFinite(value["expires_in"]) || value["expires_in"] <= 0) throw new Error("Deepgram returned an invalid token.");
|
|
88
|
+
return {
|
|
89
|
+
access_token: value["access_token"],
|
|
90
|
+
expires_in: value["expires_in"]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
var InvalidTokenRequestError = class extends Error {
|
|
94
|
+
code;
|
|
95
|
+
status;
|
|
96
|
+
constructor(message, code = "invalid-configuration", status = 400) {
|
|
97
|
+
super(message);
|
|
98
|
+
this.code = code;
|
|
99
|
+
this.status = status;
|
|
100
|
+
this.name = "InvalidTokenRequestError";
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
async function readJsonBody(request) {
|
|
104
|
+
if (request.headers.get("Content-Type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") throw new InvalidTokenRequestError("Content-Type must be application/json.", "invalid-request", 415);
|
|
105
|
+
if (Number(request.headers.get("Content-Length")) > MAX_TOKEN_REQUEST_BYTES) throw requestTooLarge();
|
|
106
|
+
const reader = request.body?.getReader();
|
|
107
|
+
if (reader === void 0) return "";
|
|
108
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
109
|
+
let bytesRead = 0;
|
|
110
|
+
let text = "";
|
|
111
|
+
while (true) {
|
|
112
|
+
const { done, value } = await reader.read();
|
|
113
|
+
if (done) break;
|
|
114
|
+
bytesRead += value.byteLength;
|
|
115
|
+
if (bytesRead > MAX_TOKEN_REQUEST_BYTES) {
|
|
116
|
+
await reader.cancel().catch(() => {});
|
|
117
|
+
throw requestTooLarge();
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
text += decoder.decode(value, { stream: true });
|
|
121
|
+
} catch {
|
|
122
|
+
await reader.cancel().catch(() => {});
|
|
123
|
+
throw invalidUtf8();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
return text + decoder.decode();
|
|
128
|
+
} catch {
|
|
129
|
+
throw invalidUtf8();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function requestTooLarge() {
|
|
133
|
+
return new InvalidTokenRequestError(`Token request body exceeds ${MAX_TOKEN_REQUEST_BYTES} bytes.`, "invalid-request", 413);
|
|
134
|
+
}
|
|
135
|
+
function invalidUtf8() {
|
|
136
|
+
return new InvalidTokenRequestError("Token request body must be valid UTF-8.", "invalid-request");
|
|
137
|
+
}
|
|
138
|
+
function jsonError(status, code, message, headers = {}) {
|
|
139
|
+
const responseHeaders = new Headers(headers);
|
|
140
|
+
responseHeaders.set("Cache-Control", "no-store");
|
|
141
|
+
responseHeaders.set("X-VoiceInput-Error", "1");
|
|
142
|
+
return Response.json({ error: {
|
|
143
|
+
code,
|
|
144
|
+
message
|
|
145
|
+
} }, {
|
|
146
|
+
status,
|
|
147
|
+
headers: responseHeaders
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function copyRetryAfter(headers) {
|
|
151
|
+
const value = headers.get("Retry-After");
|
|
152
|
+
return value === null ? {} : { "Retry-After": value };
|
|
153
|
+
}
|
|
154
|
+
function normalizeRetryAfter(value) {
|
|
155
|
+
return value === void 0 || !Number.isFinite(value) || value <= 0 ? void 0 : Math.ceil(value);
|
|
156
|
+
}
|
|
157
|
+
function nonEmpty(value, name) {
|
|
158
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${name} must be a non-empty string.`);
|
|
159
|
+
return value;
|
|
160
|
+
}
|
|
161
|
+
function isRecord(value) {
|
|
162
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
163
|
+
}
|
|
164
|
+
//#endregion
|
|
165
|
+
exports.createDeepgramTokenHandler = createDeepgramTokenHandler;
|
|
166
|
+
|
|
167
|
+
//# sourceMappingURL=server.cjs.map
|