@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.cjs","names":[],"sources":["../src/server.ts"],"sourcesContent":["import { DEEPGRAM_DEFAULT_MODEL } from \"./session-config.js\";\n\nconst DEFAULT_GRANT_URL = \"https://api.deepgram.com/v1/auth/grant\";\nconst DEFAULT_TTL_SECONDS = 30;\nconst MAX_TOKEN_REQUEST_BYTES = 16 * 1024;\n\nexport interface DeepgramAuthorization {\n readonly subject: string;\n}\n\nexport type DeepgramRateLimitResult =\n | { readonly allowed: true }\n | { readonly allowed: false; readonly retryAfterSeconds?: number };\n\nexport interface DeepgramTokenHandlerContext {\n readonly request: Request;\n readonly subject: string;\n readonly model: string;\n}\n\nexport interface DeepgramTokenIssuedMetadata {\n readonly provider: \"deepgram\";\n readonly subject: string;\n readonly model: string;\n readonly expiresIn: number;\n}\n\nexport interface CreateDeepgramTokenHandlerOptions {\n readonly apiKey: string;\n readonly authorize: (\n request: Request,\n ) => PromiseLike<DeepgramAuthorization | null> | DeepgramAuthorization | null;\n readonly model?: string;\n readonly allowedModels?: readonly string[];\n readonly ttlSeconds?: number;\n readonly rateLimit?: (\n context: DeepgramTokenHandlerContext,\n ) => PromiseLike<DeepgramRateLimitResult> | DeepgramRateLimitResult;\n readonly onTokenIssued?: (\n metadata: DeepgramTokenIssuedMetadata,\n ) => PromiseLike<void> | void;\n readonly fetch?: typeof globalThis.fetch;\n readonly grantUrl?: string;\n}\n\nexport function createDeepgramTokenHandler(\n options: CreateDeepgramTokenHandlerOptions,\n): (request: Request) => Promise<Response> {\n const defaultModel = nonEmpty(\n options.model ?? DEEPGRAM_DEFAULT_MODEL,\n \"model\",\n );\n const allowedModels = new Set(options.allowedModels ?? [defaultModel]);\n if (!allowedModels.has(defaultModel)) {\n throw new TypeError(\"allowedModels must contain the default model.\");\n }\n if (typeof options.authorize !== \"function\") {\n throw new TypeError(\"authorize must be a function.\");\n }\n const apiKey = nonEmpty(options.apiKey, \"apiKey\");\n const ttlSeconds = validateTtl(options.ttlSeconds ?? DEFAULT_TTL_SECONDS);\n const fetchImplementation = options.fetch ?? globalThis.fetch;\n const grantUrl = options.grantUrl ?? DEFAULT_GRANT_URL;\n\n return async (request) => {\n if (request.method !== \"POST\") {\n return jsonError(405, \"invalid-request\", \"Method not allowed.\", {\n Allow: \"POST\",\n });\n }\n try {\n const requestBody = await readJsonBody(request);\n const authorization = await options.authorize(\n copyRequest(request, requestBody),\n );\n if (authorization === null) {\n return jsonError(401, \"unauthorized\", \"Unauthorized.\");\n }\n const subject = nonEmpty(authorization.subject, \"subject\");\n const model = readModel(requestBody, defaultModel);\n if (!allowedModels.has(model)) {\n return jsonError(\n 400,\n \"invalid-configuration\",\n \"The requested Deepgram model is not allowed.\",\n );\n }\n const context: DeepgramTokenHandlerContext = {\n request: copyRequest(request, requestBody),\n subject,\n model,\n };\n const limit = await options.rateLimit?.(context);\n if (limit?.allowed === false) {\n const retryAfter = normalizeRetryAfter(limit.retryAfterSeconds);\n return jsonError(\n 429,\n \"rate-limited\",\n \"Rate limit exceeded.\",\n retryAfter === undefined ? {} : { \"Retry-After\": String(retryAfter) },\n );\n }\n const response = await fetchImplementation(grantUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Token ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ ttl_seconds: ttlSeconds }),\n signal: request.signal,\n });\n if (!response.ok) {\n if (response.status === 429) {\n return jsonError(\n 429,\n \"rate-limited\",\n \"Deepgram rate limit exceeded.\",\n copyRetryAfter(response.headers),\n );\n }\n return jsonError(\n 502,\n \"token-error\",\n \"Deepgram did not issue a temporary token.\",\n );\n }\n const token = validateToken(await response.json());\n await options.onTokenIssued?.({\n provider: \"deepgram\",\n subject,\n model,\n expiresIn: token.expires_in,\n });\n return Response.json(token, {\n headers: { \"Cache-Control\": \"no-store\" },\n });\n } catch (error) {\n if (request.signal.aborted) {\n return jsonError(499, \"network-error\", \"The request was cancelled.\");\n }\n if (error instanceof InvalidTokenRequestError) {\n return jsonError(error.status, error.code, error.message);\n }\n return jsonError(\n 500,\n \"token-error\",\n \"Unable to issue a Deepgram temporary token.\",\n );\n }\n };\n}\n\nfunction readModel(body: string, defaultModel: string): string {\n try {\n if (body.length === 0) {\n return defaultModel;\n }\n const value = JSON.parse(body) as unknown;\n if (!isRecord(value) || Object.keys(value).some((key) => key !== \"model\")) {\n throw new TypeError(\"The token request must contain only model.\");\n }\n return value[\"model\"] === undefined\n ? defaultModel\n : nonEmpty(value[\"model\"], \"model\");\n } catch (cause) {\n if (cause instanceof SyntaxError || cause instanceof TypeError) {\n throw new InvalidTokenRequestError(cause.message);\n }\n throw cause;\n }\n}\n\nfunction copyRequest(request: Request, body: string): Request {\n return new Request(request, {\n method: \"POST\",\n body,\n referrer: request.referrer,\n referrerPolicy: request.referrerPolicy,\n });\n}\n\nfunction validateTtl(value: number): number {\n if (!Number.isInteger(value) || value < 1 || value > 3_600) {\n throw new TypeError(\"ttlSeconds must be an integer from 1 to 3600.\");\n }\n return value;\n}\n\nfunction validateToken(value: unknown): {\n access_token: string;\n expires_in: number;\n} {\n if (\n !isRecord(value) ||\n typeof value[\"access_token\"] !== \"string\" ||\n value[\"access_token\"].length === 0 ||\n typeof value[\"expires_in\"] !== \"number\" ||\n !Number.isFinite(value[\"expires_in\"]) ||\n value[\"expires_in\"] <= 0\n ) {\n throw new Error(\"Deepgram returned an invalid token.\");\n }\n return {\n access_token: value[\"access_token\"],\n expires_in: value[\"expires_in\"],\n };\n}\n\nclass InvalidTokenRequestError extends Error {\n constructor(\n message: string,\n readonly code:\n \"invalid-configuration\" | \"invalid-request\" = \"invalid-configuration\",\n readonly status = 400,\n ) {\n super(message);\n this.name = \"InvalidTokenRequestError\";\n }\n}\n\nasync function readJsonBody(request: Request): Promise<string> {\n const contentType = request.headers\n .get(\"Content-Type\")\n ?.split(\";\", 1)[0]\n ?.trim()\n .toLowerCase();\n if (contentType !== \"application/json\") {\n throw new InvalidTokenRequestError(\n \"Content-Type must be application/json.\",\n \"invalid-request\",\n 415,\n );\n }\n const declaredLength = Number(request.headers.get(\"Content-Length\"));\n if (declaredLength > MAX_TOKEN_REQUEST_BYTES) throw requestTooLarge();\n const reader = request.body?.getReader();\n if (reader === undefined) return \"\";\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n let bytesRead = 0;\n let text = \"\";\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n bytesRead += value.byteLength;\n if (bytesRead > MAX_TOKEN_REQUEST_BYTES) {\n await reader.cancel().catch(() => {});\n throw requestTooLarge();\n }\n try {\n text += decoder.decode(value, { stream: true });\n } catch {\n await reader.cancel().catch(() => {});\n throw invalidUtf8();\n }\n }\n try {\n return text + decoder.decode();\n } catch {\n throw invalidUtf8();\n }\n}\n\nfunction requestTooLarge(): InvalidTokenRequestError {\n return new InvalidTokenRequestError(\n `Token request body exceeds ${MAX_TOKEN_REQUEST_BYTES} bytes.`,\n \"invalid-request\",\n 413,\n );\n}\n\nfunction invalidUtf8(): InvalidTokenRequestError {\n return new InvalidTokenRequestError(\n \"Token request body must be valid UTF-8.\",\n \"invalid-request\",\n );\n}\n\nfunction jsonError(\n status: number,\n code: string,\n message: string,\n headers: HeadersInit = {},\n): Response {\n const responseHeaders = new Headers(headers);\n responseHeaders.set(\"Cache-Control\", \"no-store\");\n responseHeaders.set(\"X-VoiceInput-Error\", \"1\");\n return Response.json(\n { error: { code, message } },\n { status, headers: responseHeaders },\n );\n}\n\nfunction copyRetryAfter(headers: Headers): HeadersInit {\n const value = headers.get(\"Retry-After\");\n return value === null ? {} : { \"Retry-After\": value };\n}\n\nfunction normalizeRetryAfter(value: number | undefined): number | undefined {\n return value === undefined || !Number.isFinite(value) || value <= 0\n ? undefined\n : Math.ceil(value);\n}\n\nfunction nonEmpty(value: unknown, name: string): string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`${name} must be a non-empty string.`);\n }\n return value;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/server.d.ts
|
|
2
|
+
interface DeepgramAuthorization {
|
|
3
|
+
readonly subject: string;
|
|
4
|
+
}
|
|
5
|
+
type DeepgramRateLimitResult = {
|
|
6
|
+
readonly allowed: true;
|
|
7
|
+
} | {
|
|
8
|
+
readonly allowed: false;
|
|
9
|
+
readonly retryAfterSeconds?: number;
|
|
10
|
+
};
|
|
11
|
+
interface DeepgramTokenHandlerContext {
|
|
12
|
+
readonly request: Request;
|
|
13
|
+
readonly subject: string;
|
|
14
|
+
readonly model: string;
|
|
15
|
+
}
|
|
16
|
+
interface DeepgramTokenIssuedMetadata {
|
|
17
|
+
readonly provider: "deepgram";
|
|
18
|
+
readonly subject: string;
|
|
19
|
+
readonly model: string;
|
|
20
|
+
readonly expiresIn: number;
|
|
21
|
+
}
|
|
22
|
+
interface CreateDeepgramTokenHandlerOptions {
|
|
23
|
+
readonly apiKey: string;
|
|
24
|
+
readonly authorize: (request: Request) => PromiseLike<DeepgramAuthorization | null> | DeepgramAuthorization | null;
|
|
25
|
+
readonly model?: string;
|
|
26
|
+
readonly allowedModels?: readonly string[];
|
|
27
|
+
readonly ttlSeconds?: number;
|
|
28
|
+
readonly rateLimit?: (context: DeepgramTokenHandlerContext) => PromiseLike<DeepgramRateLimitResult> | DeepgramRateLimitResult;
|
|
29
|
+
readonly onTokenIssued?: (metadata: DeepgramTokenIssuedMetadata) => PromiseLike<void> | void;
|
|
30
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
31
|
+
readonly grantUrl?: string;
|
|
32
|
+
}
|
|
33
|
+
declare function createDeepgramTokenHandler(options: CreateDeepgramTokenHandlerOptions): (request: Request) => Promise<Response>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { CreateDeepgramTokenHandlerOptions, DeepgramAuthorization, DeepgramRateLimitResult, DeepgramTokenHandlerContext, DeepgramTokenIssuedMetadata, createDeepgramTokenHandler };
|
|
36
|
+
//# sourceMappingURL=server.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.cts","names":[],"sources":["../src/server.ts"],"mappings":";UAMiB;WACN;;KAGC;WACG;;WACA;WAAyB;;UAEvB;WACN,SAAS;WACT;WACA;;UAGM;WACN;WACA;WACA;WACA;;UAGM;WACN;WACA,YACP,SAAS,YACN,YAAY,gCAAgC;WACxC;WACA;WACA;WACA,aACP,SAAS,gCACN,YAAY,2BAA2B;WACnC,iBACP,UAAU,gCACP;WACI,eAAe,WAAW;WAC1B;;iBAGK,2BACd,SAAS,qCACP,SAAS,YAAY,QAAQ"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/server.d.ts
|
|
2
|
+
interface DeepgramAuthorization {
|
|
3
|
+
readonly subject: string;
|
|
4
|
+
}
|
|
5
|
+
type DeepgramRateLimitResult = {
|
|
6
|
+
readonly allowed: true;
|
|
7
|
+
} | {
|
|
8
|
+
readonly allowed: false;
|
|
9
|
+
readonly retryAfterSeconds?: number;
|
|
10
|
+
};
|
|
11
|
+
interface DeepgramTokenHandlerContext {
|
|
12
|
+
readonly request: Request;
|
|
13
|
+
readonly subject: string;
|
|
14
|
+
readonly model: string;
|
|
15
|
+
}
|
|
16
|
+
interface DeepgramTokenIssuedMetadata {
|
|
17
|
+
readonly provider: "deepgram";
|
|
18
|
+
readonly subject: string;
|
|
19
|
+
readonly model: string;
|
|
20
|
+
readonly expiresIn: number;
|
|
21
|
+
}
|
|
22
|
+
interface CreateDeepgramTokenHandlerOptions {
|
|
23
|
+
readonly apiKey: string;
|
|
24
|
+
readonly authorize: (request: Request) => PromiseLike<DeepgramAuthorization | null> | DeepgramAuthorization | null;
|
|
25
|
+
readonly model?: string;
|
|
26
|
+
readonly allowedModels?: readonly string[];
|
|
27
|
+
readonly ttlSeconds?: number;
|
|
28
|
+
readonly rateLimit?: (context: DeepgramTokenHandlerContext) => PromiseLike<DeepgramRateLimitResult> | DeepgramRateLimitResult;
|
|
29
|
+
readonly onTokenIssued?: (metadata: DeepgramTokenIssuedMetadata) => PromiseLike<void> | void;
|
|
30
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
31
|
+
readonly grantUrl?: string;
|
|
32
|
+
}
|
|
33
|
+
declare function createDeepgramTokenHandler(options: CreateDeepgramTokenHandlerOptions): (request: Request) => Promise<Response>;
|
|
34
|
+
//#endregion
|
|
35
|
+
export { CreateDeepgramTokenHandlerOptions, DeepgramAuthorization, DeepgramRateLimitResult, DeepgramTokenHandlerContext, DeepgramTokenIssuedMetadata, createDeepgramTokenHandler };
|
|
36
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";UAMiB;WACN;;KAGC;WACG;;WACA;WAAyB;;UAEvB;WACN,SAAS;WACT;WACA;;UAGM;WACN;WACA;WACA;WACA;;UAGM;WACN;WACA,YACP,SAAS,YACN,YAAY,gCAAgC;WACxC;WACA;WACA;WACA,aACP,SAAS,gCACN,YAAY,2BAA2B;WACnC,iBACP,UAAU,gCACP;WACI,eAAe,WAAW;WAC1B;;iBAGK,2BACd,SAAS,qCACP,SAAS,YAAY,QAAQ"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import "./session-config-JCvVU8gI.js";
|
|
2
|
+
//#region src/server.ts
|
|
3
|
+
const DEFAULT_GRANT_URL = "https://api.deepgram.com/v1/auth/grant";
|
|
4
|
+
const DEFAULT_TTL_SECONDS = 30;
|
|
5
|
+
const MAX_TOKEN_REQUEST_BYTES = 16384;
|
|
6
|
+
function createDeepgramTokenHandler(options) {
|
|
7
|
+
const defaultModel = nonEmpty(options.model ?? "nova-3", "model");
|
|
8
|
+
const allowedModels = new Set(options.allowedModels ?? [defaultModel]);
|
|
9
|
+
if (!allowedModels.has(defaultModel)) throw new TypeError("allowedModels must contain the default model.");
|
|
10
|
+
if (typeof options.authorize !== "function") throw new TypeError("authorize must be a function.");
|
|
11
|
+
const apiKey = nonEmpty(options.apiKey, "apiKey");
|
|
12
|
+
const ttlSeconds = validateTtl(options.ttlSeconds ?? DEFAULT_TTL_SECONDS);
|
|
13
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
14
|
+
const grantUrl = options.grantUrl ?? DEFAULT_GRANT_URL;
|
|
15
|
+
return async (request) => {
|
|
16
|
+
if (request.method !== "POST") return jsonError(405, "invalid-request", "Method not allowed.", { Allow: "POST" });
|
|
17
|
+
try {
|
|
18
|
+
const requestBody = await readJsonBody(request);
|
|
19
|
+
const authorization = await options.authorize(copyRequest(request, requestBody));
|
|
20
|
+
if (authorization === null) return jsonError(401, "unauthorized", "Unauthorized.");
|
|
21
|
+
const subject = nonEmpty(authorization.subject, "subject");
|
|
22
|
+
const model = readModel(requestBody, defaultModel);
|
|
23
|
+
if (!allowedModels.has(model)) return jsonError(400, "invalid-configuration", "The requested Deepgram model is not allowed.");
|
|
24
|
+
const context = {
|
|
25
|
+
request: copyRequest(request, requestBody),
|
|
26
|
+
subject,
|
|
27
|
+
model
|
|
28
|
+
};
|
|
29
|
+
const limit = await options.rateLimit?.(context);
|
|
30
|
+
if (limit?.allowed === false) {
|
|
31
|
+
const retryAfter = normalizeRetryAfter(limit.retryAfterSeconds);
|
|
32
|
+
return jsonError(429, "rate-limited", "Rate limit exceeded.", retryAfter === void 0 ? {} : { "Retry-After": String(retryAfter) });
|
|
33
|
+
}
|
|
34
|
+
const response = await fetchImplementation(grantUrl, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
headers: {
|
|
37
|
+
Authorization: `Token ${apiKey}`,
|
|
38
|
+
"Content-Type": "application/json"
|
|
39
|
+
},
|
|
40
|
+
body: JSON.stringify({ ttl_seconds: ttlSeconds }),
|
|
41
|
+
signal: request.signal
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
if (response.status === 429) return jsonError(429, "rate-limited", "Deepgram rate limit exceeded.", copyRetryAfter(response.headers));
|
|
45
|
+
return jsonError(502, "token-error", "Deepgram did not issue a temporary token.");
|
|
46
|
+
}
|
|
47
|
+
const token = validateToken(await response.json());
|
|
48
|
+
await options.onTokenIssued?.({
|
|
49
|
+
provider: "deepgram",
|
|
50
|
+
subject,
|
|
51
|
+
model,
|
|
52
|
+
expiresIn: token.expires_in
|
|
53
|
+
});
|
|
54
|
+
return Response.json(token, { headers: { "Cache-Control": "no-store" } });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (request.signal.aborted) return jsonError(499, "network-error", "The request was cancelled.");
|
|
57
|
+
if (error instanceof InvalidTokenRequestError) return jsonError(error.status, error.code, error.message);
|
|
58
|
+
return jsonError(500, "token-error", "Unable to issue a Deepgram temporary token.");
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function readModel(body, defaultModel) {
|
|
63
|
+
try {
|
|
64
|
+
if (body.length === 0) return defaultModel;
|
|
65
|
+
const value = JSON.parse(body);
|
|
66
|
+
if (!isRecord(value) || Object.keys(value).some((key) => key !== "model")) throw new TypeError("The token request must contain only model.");
|
|
67
|
+
return value["model"] === void 0 ? defaultModel : nonEmpty(value["model"], "model");
|
|
68
|
+
} catch (cause) {
|
|
69
|
+
if (cause instanceof SyntaxError || cause instanceof TypeError) throw new InvalidTokenRequestError(cause.message);
|
|
70
|
+
throw cause;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function copyRequest(request, body) {
|
|
74
|
+
return new Request(request, {
|
|
75
|
+
method: "POST",
|
|
76
|
+
body,
|
|
77
|
+
referrer: request.referrer,
|
|
78
|
+
referrerPolicy: request.referrerPolicy
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function validateTtl(value) {
|
|
82
|
+
if (!Number.isInteger(value) || value < 1 || value > 3600) throw new TypeError("ttlSeconds must be an integer from 1 to 3600.");
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
function validateToken(value) {
|
|
86
|
+
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.");
|
|
87
|
+
return {
|
|
88
|
+
access_token: value["access_token"],
|
|
89
|
+
expires_in: value["expires_in"]
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
var InvalidTokenRequestError = class extends Error {
|
|
93
|
+
code;
|
|
94
|
+
status;
|
|
95
|
+
constructor(message, code = "invalid-configuration", status = 400) {
|
|
96
|
+
super(message);
|
|
97
|
+
this.code = code;
|
|
98
|
+
this.status = status;
|
|
99
|
+
this.name = "InvalidTokenRequestError";
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
async function readJsonBody(request) {
|
|
103
|
+
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);
|
|
104
|
+
if (Number(request.headers.get("Content-Length")) > MAX_TOKEN_REQUEST_BYTES) throw requestTooLarge();
|
|
105
|
+
const reader = request.body?.getReader();
|
|
106
|
+
if (reader === void 0) return "";
|
|
107
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
108
|
+
let bytesRead = 0;
|
|
109
|
+
let text = "";
|
|
110
|
+
while (true) {
|
|
111
|
+
const { done, value } = await reader.read();
|
|
112
|
+
if (done) break;
|
|
113
|
+
bytesRead += value.byteLength;
|
|
114
|
+
if (bytesRead > MAX_TOKEN_REQUEST_BYTES) {
|
|
115
|
+
await reader.cancel().catch(() => {});
|
|
116
|
+
throw requestTooLarge();
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
text += decoder.decode(value, { stream: true });
|
|
120
|
+
} catch {
|
|
121
|
+
await reader.cancel().catch(() => {});
|
|
122
|
+
throw invalidUtf8();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
return text + decoder.decode();
|
|
127
|
+
} catch {
|
|
128
|
+
throw invalidUtf8();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function requestTooLarge() {
|
|
132
|
+
return new InvalidTokenRequestError(`Token request body exceeds ${MAX_TOKEN_REQUEST_BYTES} bytes.`, "invalid-request", 413);
|
|
133
|
+
}
|
|
134
|
+
function invalidUtf8() {
|
|
135
|
+
return new InvalidTokenRequestError("Token request body must be valid UTF-8.", "invalid-request");
|
|
136
|
+
}
|
|
137
|
+
function jsonError(status, code, message, headers = {}) {
|
|
138
|
+
const responseHeaders = new Headers(headers);
|
|
139
|
+
responseHeaders.set("Cache-Control", "no-store");
|
|
140
|
+
responseHeaders.set("X-VoiceInput-Error", "1");
|
|
141
|
+
return Response.json({ error: {
|
|
142
|
+
code,
|
|
143
|
+
message
|
|
144
|
+
} }, {
|
|
145
|
+
status,
|
|
146
|
+
headers: responseHeaders
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function copyRetryAfter(headers) {
|
|
150
|
+
const value = headers.get("Retry-After");
|
|
151
|
+
return value === null ? {} : { "Retry-After": value };
|
|
152
|
+
}
|
|
153
|
+
function normalizeRetryAfter(value) {
|
|
154
|
+
return value === void 0 || !Number.isFinite(value) || value <= 0 ? void 0 : Math.ceil(value);
|
|
155
|
+
}
|
|
156
|
+
function nonEmpty(value, name) {
|
|
157
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${name} must be a non-empty string.`);
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
function isRecord(value) {
|
|
161
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { createDeepgramTokenHandler };
|
|
165
|
+
|
|
166
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["import { DEEPGRAM_DEFAULT_MODEL } from \"./session-config.js\";\n\nconst DEFAULT_GRANT_URL = \"https://api.deepgram.com/v1/auth/grant\";\nconst DEFAULT_TTL_SECONDS = 30;\nconst MAX_TOKEN_REQUEST_BYTES = 16 * 1024;\n\nexport interface DeepgramAuthorization {\n readonly subject: string;\n}\n\nexport type DeepgramRateLimitResult =\n | { readonly allowed: true }\n | { readonly allowed: false; readonly retryAfterSeconds?: number };\n\nexport interface DeepgramTokenHandlerContext {\n readonly request: Request;\n readonly subject: string;\n readonly model: string;\n}\n\nexport interface DeepgramTokenIssuedMetadata {\n readonly provider: \"deepgram\";\n readonly subject: string;\n readonly model: string;\n readonly expiresIn: number;\n}\n\nexport interface CreateDeepgramTokenHandlerOptions {\n readonly apiKey: string;\n readonly authorize: (\n request: Request,\n ) => PromiseLike<DeepgramAuthorization | null> | DeepgramAuthorization | null;\n readonly model?: string;\n readonly allowedModels?: readonly string[];\n readonly ttlSeconds?: number;\n readonly rateLimit?: (\n context: DeepgramTokenHandlerContext,\n ) => PromiseLike<DeepgramRateLimitResult> | DeepgramRateLimitResult;\n readonly onTokenIssued?: (\n metadata: DeepgramTokenIssuedMetadata,\n ) => PromiseLike<void> | void;\n readonly fetch?: typeof globalThis.fetch;\n readonly grantUrl?: string;\n}\n\nexport function createDeepgramTokenHandler(\n options: CreateDeepgramTokenHandlerOptions,\n): (request: Request) => Promise<Response> {\n const defaultModel = nonEmpty(\n options.model ?? DEEPGRAM_DEFAULT_MODEL,\n \"model\",\n );\n const allowedModels = new Set(options.allowedModels ?? [defaultModel]);\n if (!allowedModels.has(defaultModel)) {\n throw new TypeError(\"allowedModels must contain the default model.\");\n }\n if (typeof options.authorize !== \"function\") {\n throw new TypeError(\"authorize must be a function.\");\n }\n const apiKey = nonEmpty(options.apiKey, \"apiKey\");\n const ttlSeconds = validateTtl(options.ttlSeconds ?? DEFAULT_TTL_SECONDS);\n const fetchImplementation = options.fetch ?? globalThis.fetch;\n const grantUrl = options.grantUrl ?? DEFAULT_GRANT_URL;\n\n return async (request) => {\n if (request.method !== \"POST\") {\n return jsonError(405, \"invalid-request\", \"Method not allowed.\", {\n Allow: \"POST\",\n });\n }\n try {\n const requestBody = await readJsonBody(request);\n const authorization = await options.authorize(\n copyRequest(request, requestBody),\n );\n if (authorization === null) {\n return jsonError(401, \"unauthorized\", \"Unauthorized.\");\n }\n const subject = nonEmpty(authorization.subject, \"subject\");\n const model = readModel(requestBody, defaultModel);\n if (!allowedModels.has(model)) {\n return jsonError(\n 400,\n \"invalid-configuration\",\n \"The requested Deepgram model is not allowed.\",\n );\n }\n const context: DeepgramTokenHandlerContext = {\n request: copyRequest(request, requestBody),\n subject,\n model,\n };\n const limit = await options.rateLimit?.(context);\n if (limit?.allowed === false) {\n const retryAfter = normalizeRetryAfter(limit.retryAfterSeconds);\n return jsonError(\n 429,\n \"rate-limited\",\n \"Rate limit exceeded.\",\n retryAfter === undefined ? {} : { \"Retry-After\": String(retryAfter) },\n );\n }\n const response = await fetchImplementation(grantUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Token ${apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ ttl_seconds: ttlSeconds }),\n signal: request.signal,\n });\n if (!response.ok) {\n if (response.status === 429) {\n return jsonError(\n 429,\n \"rate-limited\",\n \"Deepgram rate limit exceeded.\",\n copyRetryAfter(response.headers),\n );\n }\n return jsonError(\n 502,\n \"token-error\",\n \"Deepgram did not issue a temporary token.\",\n );\n }\n const token = validateToken(await response.json());\n await options.onTokenIssued?.({\n provider: \"deepgram\",\n subject,\n model,\n expiresIn: token.expires_in,\n });\n return Response.json(token, {\n headers: { \"Cache-Control\": \"no-store\" },\n });\n } catch (error) {\n if (request.signal.aborted) {\n return jsonError(499, \"network-error\", \"The request was cancelled.\");\n }\n if (error instanceof InvalidTokenRequestError) {\n return jsonError(error.status, error.code, error.message);\n }\n return jsonError(\n 500,\n \"token-error\",\n \"Unable to issue a Deepgram temporary token.\",\n );\n }\n };\n}\n\nfunction readModel(body: string, defaultModel: string): string {\n try {\n if (body.length === 0) {\n return defaultModel;\n }\n const value = JSON.parse(body) as unknown;\n if (!isRecord(value) || Object.keys(value).some((key) => key !== \"model\")) {\n throw new TypeError(\"The token request must contain only model.\");\n }\n return value[\"model\"] === undefined\n ? defaultModel\n : nonEmpty(value[\"model\"], \"model\");\n } catch (cause) {\n if (cause instanceof SyntaxError || cause instanceof TypeError) {\n throw new InvalidTokenRequestError(cause.message);\n }\n throw cause;\n }\n}\n\nfunction copyRequest(request: Request, body: string): Request {\n return new Request(request, {\n method: \"POST\",\n body,\n referrer: request.referrer,\n referrerPolicy: request.referrerPolicy,\n });\n}\n\nfunction validateTtl(value: number): number {\n if (!Number.isInteger(value) || value < 1 || value > 3_600) {\n throw new TypeError(\"ttlSeconds must be an integer from 1 to 3600.\");\n }\n return value;\n}\n\nfunction validateToken(value: unknown): {\n access_token: string;\n expires_in: number;\n} {\n if (\n !isRecord(value) ||\n typeof value[\"access_token\"] !== \"string\" ||\n value[\"access_token\"].length === 0 ||\n typeof value[\"expires_in\"] !== \"number\" ||\n !Number.isFinite(value[\"expires_in\"]) ||\n value[\"expires_in\"] <= 0\n ) {\n throw new Error(\"Deepgram returned an invalid token.\");\n }\n return {\n access_token: value[\"access_token\"],\n expires_in: value[\"expires_in\"],\n };\n}\n\nclass InvalidTokenRequestError extends Error {\n constructor(\n message: string,\n readonly code:\n \"invalid-configuration\" | \"invalid-request\" = \"invalid-configuration\",\n readonly status = 400,\n ) {\n super(message);\n this.name = \"InvalidTokenRequestError\";\n }\n}\n\nasync function readJsonBody(request: Request): Promise<string> {\n const contentType = request.headers\n .get(\"Content-Type\")\n ?.split(\";\", 1)[0]\n ?.trim()\n .toLowerCase();\n if (contentType !== \"application/json\") {\n throw new InvalidTokenRequestError(\n \"Content-Type must be application/json.\",\n \"invalid-request\",\n 415,\n );\n }\n const declaredLength = Number(request.headers.get(\"Content-Length\"));\n if (declaredLength > MAX_TOKEN_REQUEST_BYTES) throw requestTooLarge();\n const reader = request.body?.getReader();\n if (reader === undefined) return \"\";\n const decoder = new TextDecoder(\"utf-8\", { fatal: true });\n let bytesRead = 0;\n let text = \"\";\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n bytesRead += value.byteLength;\n if (bytesRead > MAX_TOKEN_REQUEST_BYTES) {\n await reader.cancel().catch(() => {});\n throw requestTooLarge();\n }\n try {\n text += decoder.decode(value, { stream: true });\n } catch {\n await reader.cancel().catch(() => {});\n throw invalidUtf8();\n }\n }\n try {\n return text + decoder.decode();\n } catch {\n throw invalidUtf8();\n }\n}\n\nfunction requestTooLarge(): InvalidTokenRequestError {\n return new InvalidTokenRequestError(\n `Token request body exceeds ${MAX_TOKEN_REQUEST_BYTES} bytes.`,\n \"invalid-request\",\n 413,\n );\n}\n\nfunction invalidUtf8(): InvalidTokenRequestError {\n return new InvalidTokenRequestError(\n \"Token request body must be valid UTF-8.\",\n \"invalid-request\",\n );\n}\n\nfunction jsonError(\n status: number,\n code: string,\n message: string,\n headers: HeadersInit = {},\n): Response {\n const responseHeaders = new Headers(headers);\n responseHeaders.set(\"Cache-Control\", \"no-store\");\n responseHeaders.set(\"X-VoiceInput-Error\", \"1\");\n return Response.json(\n { error: { code, message } },\n { status, headers: responseHeaders },\n );\n}\n\nfunction copyRetryAfter(headers: Headers): HeadersInit {\n const value = headers.get(\"Retry-After\");\n return value === null ? {} : { \"Retry-After\": value };\n}\n\nfunction normalizeRetryAfter(value: number | undefined): number | undefined {\n return value === undefined || !Number.isFinite(value) || value <= 0\n ? undefined\n : Math.ceil(value);\n}\n\nfunction nonEmpty(value: unknown, name: string): string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`${name} must be a non-empty string.`);\n }\n return value;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { VoiceInputError } from "@voiceinput/provider";
|
|
2
|
+
//#region src/session-config.ts
|
|
3
|
+
const DEEPGRAM_DEFAULT_MODEL = "nova-3";
|
|
4
|
+
const DEEPGRAM_SAMPLE_RATE = 16e3;
|
|
5
|
+
const GENERAL_NOVA_ENGLISH_LANGUAGES = /* @__PURE__ */ new Set([
|
|
6
|
+
"en",
|
|
7
|
+
"en-AU",
|
|
8
|
+
"en-GB",
|
|
9
|
+
"en-IN",
|
|
10
|
+
"en-NZ",
|
|
11
|
+
"en-US"
|
|
12
|
+
]);
|
|
13
|
+
function validateDeepgramConfiguration(value) {
|
|
14
|
+
const model = nonEmptyString(value.model, "model");
|
|
15
|
+
const language = validateLanguage(value.language, model);
|
|
16
|
+
const vocabulary = validateVocabulary(value.vocabulary, model);
|
|
17
|
+
const endpointing = validateEndpointing(value.endpointing);
|
|
18
|
+
const smartFormat = optionalBoolean(value.smartFormat, "smartFormat") ?? true;
|
|
19
|
+
const punctuate = optionalBoolean(value.punctuate, "punctuate") ?? true;
|
|
20
|
+
const profanityFilter = optionalBoolean(value.profanityFilter, "profanityFilter");
|
|
21
|
+
const numerals = optionalBoolean(value.numerals, "numerals");
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
model,
|
|
24
|
+
language,
|
|
25
|
+
...vocabulary === void 0 ? {} : { vocabulary },
|
|
26
|
+
...endpointing === void 0 ? {} : { endpointing },
|
|
27
|
+
smartFormat,
|
|
28
|
+
punctuate,
|
|
29
|
+
...profanityFilter === void 0 ? {} : { profanityFilter },
|
|
30
|
+
...numerals === void 0 ? {} : { numerals }
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function createDeepgramRealtimeUrl(baseUrl, configuration) {
|
|
34
|
+
const url = new URL(baseUrl);
|
|
35
|
+
url.searchParams.set("model", configuration.model);
|
|
36
|
+
url.searchParams.set("encoding", "linear16");
|
|
37
|
+
url.searchParams.set("sample_rate", String(DEEPGRAM_SAMPLE_RATE));
|
|
38
|
+
url.searchParams.set("channels", "1");
|
|
39
|
+
url.searchParams.set("interim_results", "true");
|
|
40
|
+
url.searchParams.set("vad_events", "true");
|
|
41
|
+
url.searchParams.set("smart_format", String(configuration.smartFormat));
|
|
42
|
+
url.searchParams.set("punctuate", String(configuration.punctuate));
|
|
43
|
+
url.searchParams.set("language", configuration.language);
|
|
44
|
+
for (const term of configuration.vocabulary ?? []) url.searchParams.append("keyterm", term);
|
|
45
|
+
if (configuration.endpointing === false) url.searchParams.set("endpointing", "false");
|
|
46
|
+
else if (configuration.endpointing !== void 0) url.searchParams.set("endpointing", String(configuration.endpointing.silenceMs));
|
|
47
|
+
setOptional(url, "profanity_filter", configuration.profanityFilter);
|
|
48
|
+
setOptional(url, "numerals", configuration.numerals);
|
|
49
|
+
return url.href;
|
|
50
|
+
}
|
|
51
|
+
function validateLanguage(value, model) {
|
|
52
|
+
if (value === void 0) {
|
|
53
|
+
if (supportsMultilingual(model)) return "multi";
|
|
54
|
+
throw unsupportedFeature(`language is required for Deepgram model ${model} because it does not support multilingual transcription.`);
|
|
55
|
+
}
|
|
56
|
+
if (typeof value !== "string") throw new TypeError("language must be a valid BCP 47 language tag.");
|
|
57
|
+
try {
|
|
58
|
+
const language = new Intl.Locale(value);
|
|
59
|
+
const canonical = language.toString();
|
|
60
|
+
return supportsMultilingual(model) && language.language === "en" && !GENERAL_NOVA_ENGLISH_LANGUAGES.has(canonical) ? "en" : canonical;
|
|
61
|
+
} catch {
|
|
62
|
+
throw new TypeError("language must be a valid BCP 47 language tag.");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function validateVocabulary(value, model) {
|
|
66
|
+
if (value === void 0) return;
|
|
67
|
+
if (!Array.isArray(value)) throw new TypeError("vocabulary must be an array of strings.");
|
|
68
|
+
const vocabulary = value.map((term) => {
|
|
69
|
+
if (typeof term !== "string" || term.length === 0 || term !== term.trim() || /[\r\n]/u.test(term)) throw new TypeError("Deepgram vocabulary terms must be non-empty trimmed strings without line breaks.");
|
|
70
|
+
return term;
|
|
71
|
+
});
|
|
72
|
+
if (!model.startsWith("nova-3")) throw unsupportedFeature("Deepgram vocabulary requires a Nova-3 model because it maps to keyterm prompting.");
|
|
73
|
+
return Object.freeze(vocabulary);
|
|
74
|
+
}
|
|
75
|
+
function supportsMultilingual(model) {
|
|
76
|
+
return model === "nova-2" || model === "nova-2-general" || model === "nova-3" || model === "nova-3-general";
|
|
77
|
+
}
|
|
78
|
+
function validateEndpointing(value) {
|
|
79
|
+
if (value === void 0 || value === false) return value;
|
|
80
|
+
if (typeof value !== "object" || value === null || Object.keys(value).some((key) => key !== "silenceMs") || !Number.isInteger(value.silenceMs) || value.silenceMs <= 0) throw new TypeError("Deepgram endpointing silenceMs must be a positive integer.");
|
|
81
|
+
return Object.freeze({ silenceMs: value.silenceMs });
|
|
82
|
+
}
|
|
83
|
+
function nonEmptyString(value, name) {
|
|
84
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${name} must be a non-empty string.`);
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function optionalBoolean(value, name) {
|
|
88
|
+
if (value !== void 0 && typeof value !== "boolean") throw new TypeError(`${name} must be a boolean.`);
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function setOptional(url, key, value) {
|
|
92
|
+
if (value !== void 0) url.searchParams.set(key, String(value));
|
|
93
|
+
}
|
|
94
|
+
function unsupportedFeature(message) {
|
|
95
|
+
return new VoiceInputError({
|
|
96
|
+
code: "unsupported-feature",
|
|
97
|
+
message,
|
|
98
|
+
provider: "deepgram"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { validateDeepgramConfiguration as i, DEEPGRAM_SAMPLE_RATE as n, createDeepgramRealtimeUrl as r, DEEPGRAM_DEFAULT_MODEL as t };
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=session-config-JCvVU8gI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-config-JCvVU8gI.js","names":[],"sources":["../src/session-config.ts"],"sourcesContent":["import {\n VoiceInputError,\n type VoiceTranscriptionOptions,\n} from \"@voiceinput/provider\";\n\nexport const DEEPGRAM_DEFAULT_MODEL = \"nova-3\";\nexport const DEEPGRAM_SAMPLE_RATE = 16_000;\n\nconst GENERAL_NOVA_ENGLISH_LANGUAGES = new Set([\n \"en\",\n \"en-AU\",\n \"en-GB\",\n \"en-IN\",\n \"en-NZ\",\n \"en-US\",\n]);\n\nexport interface DeepgramRealtimeSettings {\n readonly smartFormat?: boolean;\n readonly punctuate?: boolean;\n readonly profanityFilter?: boolean;\n readonly numerals?: boolean;\n}\n\nexport interface DeepgramSessionConfiguration {\n readonly model: string;\n readonly language: string;\n readonly vocabulary?: readonly string[];\n readonly endpointing?: false | { readonly silenceMs: number };\n readonly smartFormat: boolean;\n readonly punctuate: boolean;\n readonly profanityFilter?: boolean;\n readonly numerals?: boolean;\n}\n\nexport function validateDeepgramConfiguration(\n value: VoiceTranscriptionOptions &\n DeepgramRealtimeSettings & { readonly model: string },\n): DeepgramSessionConfiguration {\n const model = nonEmptyString(value.model, \"model\");\n const language = validateLanguage(value.language, model);\n const vocabulary = validateVocabulary(value.vocabulary, model);\n const endpointing = validateEndpointing(value.endpointing);\n const smartFormat = optionalBoolean(value.smartFormat, \"smartFormat\") ?? true;\n const punctuate = optionalBoolean(value.punctuate, \"punctuate\") ?? true;\n const profanityFilter = optionalBoolean(\n value.profanityFilter,\n \"profanityFilter\",\n );\n const numerals = optionalBoolean(value.numerals, \"numerals\");\n\n return Object.freeze({\n model,\n language,\n ...(vocabulary === undefined ? {} : { vocabulary }),\n ...(endpointing === undefined ? {} : { endpointing }),\n smartFormat,\n punctuate,\n ...(profanityFilter === undefined ? {} : { profanityFilter }),\n ...(numerals === undefined ? {} : { numerals }),\n });\n}\n\nexport function createDeepgramRealtimeUrl(\n baseUrl: string,\n configuration: DeepgramSessionConfiguration,\n): string {\n const url = new URL(baseUrl);\n url.searchParams.set(\"model\", configuration.model);\n url.searchParams.set(\"encoding\", \"linear16\");\n url.searchParams.set(\"sample_rate\", String(DEEPGRAM_SAMPLE_RATE));\n url.searchParams.set(\"channels\", \"1\");\n url.searchParams.set(\"interim_results\", \"true\");\n url.searchParams.set(\"vad_events\", \"true\");\n url.searchParams.set(\"smart_format\", String(configuration.smartFormat));\n url.searchParams.set(\"punctuate\", String(configuration.punctuate));\n url.searchParams.set(\"language\", configuration.language);\n for (const term of configuration.vocabulary ?? []) {\n url.searchParams.append(\"keyterm\", term);\n }\n if (configuration.endpointing === false) {\n url.searchParams.set(\"endpointing\", \"false\");\n } else if (configuration.endpointing !== undefined) {\n url.searchParams.set(\n \"endpointing\",\n String(configuration.endpointing.silenceMs),\n );\n }\n setOptional(url, \"profanity_filter\", configuration.profanityFilter);\n setOptional(url, \"numerals\", configuration.numerals);\n return url.href;\n}\n\nfunction validateLanguage(value: string | undefined, model: string): string {\n if (value === undefined) {\n if (supportsMultilingual(model)) {\n return \"multi\";\n }\n throw unsupportedFeature(\n `language is required for Deepgram model ${model} because it does not support multilingual transcription.`,\n );\n }\n if (typeof value !== \"string\") {\n throw new TypeError(\"language must be a valid BCP 47 language tag.\");\n }\n try {\n const language = new Intl.Locale(value);\n const canonical = language.toString();\n return supportsMultilingual(model) &&\n language.language === \"en\" &&\n !GENERAL_NOVA_ENGLISH_LANGUAGES.has(canonical)\n ? \"en\"\n : canonical;\n } catch {\n throw new TypeError(\"language must be a valid BCP 47 language tag.\");\n }\n}\n\nfunction validateVocabulary(\n value: readonly string[] | undefined,\n model: string,\n): readonly string[] | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (!Array.isArray(value)) {\n throw new TypeError(\"vocabulary must be an array of strings.\");\n }\n const vocabulary = value.map((term) => {\n if (\n typeof term !== \"string\" ||\n term.length === 0 ||\n term !== term.trim() ||\n /[\\r\\n]/u.test(term)\n ) {\n throw new TypeError(\n \"Deepgram vocabulary terms must be non-empty trimmed strings without line breaks.\",\n );\n }\n return term;\n });\n if (!model.startsWith(\"nova-3\")) {\n throw unsupportedFeature(\n \"Deepgram vocabulary requires a Nova-3 model because it maps to keyterm prompting.\",\n );\n }\n return Object.freeze(vocabulary);\n}\n\nfunction supportsMultilingual(model: string): boolean {\n return (\n model === \"nova-2\" ||\n model === \"nova-2-general\" ||\n model === \"nova-3\" ||\n model === \"nova-3-general\"\n );\n}\n\nfunction validateEndpointing(\n value: VoiceTranscriptionOptions[\"endpointing\"],\n): DeepgramSessionConfiguration[\"endpointing\"] {\n if (value === undefined || value === false) {\n return value;\n }\n if (\n typeof value !== \"object\" ||\n value === null ||\n Object.keys(value).some((key) => key !== \"silenceMs\") ||\n !Number.isInteger(value.silenceMs) ||\n value.silenceMs <= 0\n ) {\n throw new TypeError(\n \"Deepgram endpointing silenceMs must be a positive integer.\",\n );\n }\n return Object.freeze({ silenceMs: value.silenceMs });\n}\n\nfunction nonEmptyString(value: string, name: string): string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`${name} must be a non-empty string.`);\n }\n return value;\n}\n\nfunction optionalBoolean(\n value: boolean | undefined,\n name: string,\n): boolean | undefined {\n if (value !== undefined && typeof value !== \"boolean\") {\n throw new TypeError(`${name} must be a boolean.`);\n }\n return value;\n}\n\nfunction setOptional(url: URL, key: string, value: boolean | undefined): void {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n}\n\nfunction unsupportedFeature(message: string): VoiceInputError {\n return new VoiceInputError({\n code: \"unsupported-feature\",\n message,\n provider: \"deepgram\",\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
let _voiceinput_provider = require("@voiceinput/provider");
|
|
2
|
+
//#region src/session-config.ts
|
|
3
|
+
const DEEPGRAM_DEFAULT_MODEL = "nova-3";
|
|
4
|
+
const DEEPGRAM_SAMPLE_RATE = 16e3;
|
|
5
|
+
const GENERAL_NOVA_ENGLISH_LANGUAGES = /* @__PURE__ */ new Set([
|
|
6
|
+
"en",
|
|
7
|
+
"en-AU",
|
|
8
|
+
"en-GB",
|
|
9
|
+
"en-IN",
|
|
10
|
+
"en-NZ",
|
|
11
|
+
"en-US"
|
|
12
|
+
]);
|
|
13
|
+
function validateDeepgramConfiguration(value) {
|
|
14
|
+
const model = nonEmptyString(value.model, "model");
|
|
15
|
+
const language = validateLanguage(value.language, model);
|
|
16
|
+
const vocabulary = validateVocabulary(value.vocabulary, model);
|
|
17
|
+
const endpointing = validateEndpointing(value.endpointing);
|
|
18
|
+
const smartFormat = optionalBoolean(value.smartFormat, "smartFormat") ?? true;
|
|
19
|
+
const punctuate = optionalBoolean(value.punctuate, "punctuate") ?? true;
|
|
20
|
+
const profanityFilter = optionalBoolean(value.profanityFilter, "profanityFilter");
|
|
21
|
+
const numerals = optionalBoolean(value.numerals, "numerals");
|
|
22
|
+
return Object.freeze({
|
|
23
|
+
model,
|
|
24
|
+
language,
|
|
25
|
+
...vocabulary === void 0 ? {} : { vocabulary },
|
|
26
|
+
...endpointing === void 0 ? {} : { endpointing },
|
|
27
|
+
smartFormat,
|
|
28
|
+
punctuate,
|
|
29
|
+
...profanityFilter === void 0 ? {} : { profanityFilter },
|
|
30
|
+
...numerals === void 0 ? {} : { numerals }
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function createDeepgramRealtimeUrl(baseUrl, configuration) {
|
|
34
|
+
const url = new URL(baseUrl);
|
|
35
|
+
url.searchParams.set("model", configuration.model);
|
|
36
|
+
url.searchParams.set("encoding", "linear16");
|
|
37
|
+
url.searchParams.set("sample_rate", String(DEEPGRAM_SAMPLE_RATE));
|
|
38
|
+
url.searchParams.set("channels", "1");
|
|
39
|
+
url.searchParams.set("interim_results", "true");
|
|
40
|
+
url.searchParams.set("vad_events", "true");
|
|
41
|
+
url.searchParams.set("smart_format", String(configuration.smartFormat));
|
|
42
|
+
url.searchParams.set("punctuate", String(configuration.punctuate));
|
|
43
|
+
url.searchParams.set("language", configuration.language);
|
|
44
|
+
for (const term of configuration.vocabulary ?? []) url.searchParams.append("keyterm", term);
|
|
45
|
+
if (configuration.endpointing === false) url.searchParams.set("endpointing", "false");
|
|
46
|
+
else if (configuration.endpointing !== void 0) url.searchParams.set("endpointing", String(configuration.endpointing.silenceMs));
|
|
47
|
+
setOptional(url, "profanity_filter", configuration.profanityFilter);
|
|
48
|
+
setOptional(url, "numerals", configuration.numerals);
|
|
49
|
+
return url.href;
|
|
50
|
+
}
|
|
51
|
+
function validateLanguage(value, model) {
|
|
52
|
+
if (value === void 0) {
|
|
53
|
+
if (supportsMultilingual(model)) return "multi";
|
|
54
|
+
throw unsupportedFeature(`language is required for Deepgram model ${model} because it does not support multilingual transcription.`);
|
|
55
|
+
}
|
|
56
|
+
if (typeof value !== "string") throw new TypeError("language must be a valid BCP 47 language tag.");
|
|
57
|
+
try {
|
|
58
|
+
const language = new Intl.Locale(value);
|
|
59
|
+
const canonical = language.toString();
|
|
60
|
+
return supportsMultilingual(model) && language.language === "en" && !GENERAL_NOVA_ENGLISH_LANGUAGES.has(canonical) ? "en" : canonical;
|
|
61
|
+
} catch {
|
|
62
|
+
throw new TypeError("language must be a valid BCP 47 language tag.");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function validateVocabulary(value, model) {
|
|
66
|
+
if (value === void 0) return;
|
|
67
|
+
if (!Array.isArray(value)) throw new TypeError("vocabulary must be an array of strings.");
|
|
68
|
+
const vocabulary = value.map((term) => {
|
|
69
|
+
if (typeof term !== "string" || term.length === 0 || term !== term.trim() || /[\r\n]/u.test(term)) throw new TypeError("Deepgram vocabulary terms must be non-empty trimmed strings without line breaks.");
|
|
70
|
+
return term;
|
|
71
|
+
});
|
|
72
|
+
if (!model.startsWith("nova-3")) throw unsupportedFeature("Deepgram vocabulary requires a Nova-3 model because it maps to keyterm prompting.");
|
|
73
|
+
return Object.freeze(vocabulary);
|
|
74
|
+
}
|
|
75
|
+
function supportsMultilingual(model) {
|
|
76
|
+
return model === "nova-2" || model === "nova-2-general" || model === "nova-3" || model === "nova-3-general";
|
|
77
|
+
}
|
|
78
|
+
function validateEndpointing(value) {
|
|
79
|
+
if (value === void 0 || value === false) return value;
|
|
80
|
+
if (typeof value !== "object" || value === null || Object.keys(value).some((key) => key !== "silenceMs") || !Number.isInteger(value.silenceMs) || value.silenceMs <= 0) throw new TypeError("Deepgram endpointing silenceMs must be a positive integer.");
|
|
81
|
+
return Object.freeze({ silenceMs: value.silenceMs });
|
|
82
|
+
}
|
|
83
|
+
function nonEmptyString(value, name) {
|
|
84
|
+
if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${name} must be a non-empty string.`);
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function optionalBoolean(value, name) {
|
|
88
|
+
if (value !== void 0 && typeof value !== "boolean") throw new TypeError(`${name} must be a boolean.`);
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function setOptional(url, key, value) {
|
|
92
|
+
if (value !== void 0) url.searchParams.set(key, String(value));
|
|
93
|
+
}
|
|
94
|
+
function unsupportedFeature(message) {
|
|
95
|
+
return new _voiceinput_provider.VoiceInputError({
|
|
96
|
+
code: "unsupported-feature",
|
|
97
|
+
message,
|
|
98
|
+
provider: "deepgram"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
Object.defineProperty(exports, "DEEPGRAM_DEFAULT_MODEL", {
|
|
103
|
+
enumerable: true,
|
|
104
|
+
get: function() {
|
|
105
|
+
return DEEPGRAM_DEFAULT_MODEL;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
Object.defineProperty(exports, "DEEPGRAM_SAMPLE_RATE", {
|
|
109
|
+
enumerable: true,
|
|
110
|
+
get: function() {
|
|
111
|
+
return DEEPGRAM_SAMPLE_RATE;
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
Object.defineProperty(exports, "createDeepgramRealtimeUrl", {
|
|
115
|
+
enumerable: true,
|
|
116
|
+
get: function() {
|
|
117
|
+
return createDeepgramRealtimeUrl;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
Object.defineProperty(exports, "validateDeepgramConfiguration", {
|
|
121
|
+
enumerable: true,
|
|
122
|
+
get: function() {
|
|
123
|
+
return validateDeepgramConfiguration;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
//# sourceMappingURL=session-config-aoO2eGMT.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-config-aoO2eGMT.cjs","names":["VoiceInputError"],"sources":["../src/session-config.ts"],"sourcesContent":["import {\n VoiceInputError,\n type VoiceTranscriptionOptions,\n} from \"@voiceinput/provider\";\n\nexport const DEEPGRAM_DEFAULT_MODEL = \"nova-3\";\nexport const DEEPGRAM_SAMPLE_RATE = 16_000;\n\nconst GENERAL_NOVA_ENGLISH_LANGUAGES = new Set([\n \"en\",\n \"en-AU\",\n \"en-GB\",\n \"en-IN\",\n \"en-NZ\",\n \"en-US\",\n]);\n\nexport interface DeepgramRealtimeSettings {\n readonly smartFormat?: boolean;\n readonly punctuate?: boolean;\n readonly profanityFilter?: boolean;\n readonly numerals?: boolean;\n}\n\nexport interface DeepgramSessionConfiguration {\n readonly model: string;\n readonly language: string;\n readonly vocabulary?: readonly string[];\n readonly endpointing?: false | { readonly silenceMs: number };\n readonly smartFormat: boolean;\n readonly punctuate: boolean;\n readonly profanityFilter?: boolean;\n readonly numerals?: boolean;\n}\n\nexport function validateDeepgramConfiguration(\n value: VoiceTranscriptionOptions &\n DeepgramRealtimeSettings & { readonly model: string },\n): DeepgramSessionConfiguration {\n const model = nonEmptyString(value.model, \"model\");\n const language = validateLanguage(value.language, model);\n const vocabulary = validateVocabulary(value.vocabulary, model);\n const endpointing = validateEndpointing(value.endpointing);\n const smartFormat = optionalBoolean(value.smartFormat, \"smartFormat\") ?? true;\n const punctuate = optionalBoolean(value.punctuate, \"punctuate\") ?? true;\n const profanityFilter = optionalBoolean(\n value.profanityFilter,\n \"profanityFilter\",\n );\n const numerals = optionalBoolean(value.numerals, \"numerals\");\n\n return Object.freeze({\n model,\n language,\n ...(vocabulary === undefined ? {} : { vocabulary }),\n ...(endpointing === undefined ? {} : { endpointing }),\n smartFormat,\n punctuate,\n ...(profanityFilter === undefined ? {} : { profanityFilter }),\n ...(numerals === undefined ? {} : { numerals }),\n });\n}\n\nexport function createDeepgramRealtimeUrl(\n baseUrl: string,\n configuration: DeepgramSessionConfiguration,\n): string {\n const url = new URL(baseUrl);\n url.searchParams.set(\"model\", configuration.model);\n url.searchParams.set(\"encoding\", \"linear16\");\n url.searchParams.set(\"sample_rate\", String(DEEPGRAM_SAMPLE_RATE));\n url.searchParams.set(\"channels\", \"1\");\n url.searchParams.set(\"interim_results\", \"true\");\n url.searchParams.set(\"vad_events\", \"true\");\n url.searchParams.set(\"smart_format\", String(configuration.smartFormat));\n url.searchParams.set(\"punctuate\", String(configuration.punctuate));\n url.searchParams.set(\"language\", configuration.language);\n for (const term of configuration.vocabulary ?? []) {\n url.searchParams.append(\"keyterm\", term);\n }\n if (configuration.endpointing === false) {\n url.searchParams.set(\"endpointing\", \"false\");\n } else if (configuration.endpointing !== undefined) {\n url.searchParams.set(\n \"endpointing\",\n String(configuration.endpointing.silenceMs),\n );\n }\n setOptional(url, \"profanity_filter\", configuration.profanityFilter);\n setOptional(url, \"numerals\", configuration.numerals);\n return url.href;\n}\n\nfunction validateLanguage(value: string | undefined, model: string): string {\n if (value === undefined) {\n if (supportsMultilingual(model)) {\n return \"multi\";\n }\n throw unsupportedFeature(\n `language is required for Deepgram model ${model} because it does not support multilingual transcription.`,\n );\n }\n if (typeof value !== \"string\") {\n throw new TypeError(\"language must be a valid BCP 47 language tag.\");\n }\n try {\n const language = new Intl.Locale(value);\n const canonical = language.toString();\n return supportsMultilingual(model) &&\n language.language === \"en\" &&\n !GENERAL_NOVA_ENGLISH_LANGUAGES.has(canonical)\n ? \"en\"\n : canonical;\n } catch {\n throw new TypeError(\"language must be a valid BCP 47 language tag.\");\n }\n}\n\nfunction validateVocabulary(\n value: readonly string[] | undefined,\n model: string,\n): readonly string[] | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (!Array.isArray(value)) {\n throw new TypeError(\"vocabulary must be an array of strings.\");\n }\n const vocabulary = value.map((term) => {\n if (\n typeof term !== \"string\" ||\n term.length === 0 ||\n term !== term.trim() ||\n /[\\r\\n]/u.test(term)\n ) {\n throw new TypeError(\n \"Deepgram vocabulary terms must be non-empty trimmed strings without line breaks.\",\n );\n }\n return term;\n });\n if (!model.startsWith(\"nova-3\")) {\n throw unsupportedFeature(\n \"Deepgram vocabulary requires a Nova-3 model because it maps to keyterm prompting.\",\n );\n }\n return Object.freeze(vocabulary);\n}\n\nfunction supportsMultilingual(model: string): boolean {\n return (\n model === \"nova-2\" ||\n model === \"nova-2-general\" ||\n model === \"nova-3\" ||\n model === \"nova-3-general\"\n );\n}\n\nfunction validateEndpointing(\n value: VoiceTranscriptionOptions[\"endpointing\"],\n): DeepgramSessionConfiguration[\"endpointing\"] {\n if (value === undefined || value === false) {\n return value;\n }\n if (\n typeof value !== \"object\" ||\n value === null ||\n Object.keys(value).some((key) => key !== \"silenceMs\") ||\n !Number.isInteger(value.silenceMs) ||\n value.silenceMs <= 0\n ) {\n throw new TypeError(\n \"Deepgram endpointing silenceMs must be a positive integer.\",\n );\n }\n return Object.freeze({ silenceMs: value.silenceMs });\n}\n\nfunction nonEmptyString(value: string, name: string): string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new TypeError(`${name} must be a non-empty string.`);\n }\n return value;\n}\n\nfunction optionalBoolean(\n value: boolean | undefined,\n name: string,\n): boolean | undefined {\n if (value !== undefined && typeof value !== \"boolean\") {\n throw new TypeError(`${name} must be a boolean.`);\n }\n return value;\n}\n\nfunction setOptional(url: URL, key: string, value: boolean | undefined): void {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n}\n\nfunction unsupportedFeature(message: string): VoiceInputError {\n return new VoiceInputError({\n code: \"unsupported-feature\",\n message,\n provider: \"deepgram\",\n });\n}\n"],"mappings":"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"}
|