@voiceinput/openai 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 +138 -0
- package/dist/index.cjs +486 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +484 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +165 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +38 -0
- package/dist/server.d.cts.map +1 -0
- package/dist/server.d.ts +38 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +164 -0
- package/dist/server.js.map +1 -0
- package/dist/session-config-CmoDDiY6.cjs +114 -0
- package/dist/session-config-CmoDDiY6.cjs.map +1 -0
- package/dist/session-config-CuDb4m9Q.js +91 -0
- package/dist/session-config-CuDb4m9Q.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
let _voiceinput_provider = require("@voiceinput/provider");
|
|
2
|
+
//#region src/session-config.ts
|
|
3
|
+
const OPENAI_DEFAULT_MODEL = "gpt-transcribe";
|
|
4
|
+
const OPENAI_SAMPLE_RATE = 24e3;
|
|
5
|
+
function createOpenAITranscriptionSession(options) {
|
|
6
|
+
const vocabulary = options.vocabulary ?? [];
|
|
7
|
+
const liveModel = options.model.startsWith("gpt-live-transcribe");
|
|
8
|
+
const prompt = liveModel || vocabulary.length === 0 ? void 0 : `Expected vocabulary: ${vocabulary.join(", ")}.`;
|
|
9
|
+
const turnDetection = createTurnDetection(options.endpointing, liveModel);
|
|
10
|
+
return {
|
|
11
|
+
type: "transcription",
|
|
12
|
+
audio: { input: {
|
|
13
|
+
format: {
|
|
14
|
+
type: "audio/pcm",
|
|
15
|
+
rate: OPENAI_SAMPLE_RATE
|
|
16
|
+
},
|
|
17
|
+
transcription: {
|
|
18
|
+
model: options.model,
|
|
19
|
+
...prompt === void 0 ? {} : { prompt },
|
|
20
|
+
...liveModel && vocabulary.length > 0 ? { keywords: vocabulary } : {},
|
|
21
|
+
...options.language === void 0 ? {} : liveModel ? { languages: [options.language] } : { language: options.language }
|
|
22
|
+
},
|
|
23
|
+
...turnDetection === void 0 ? {} : { turn_detection: turnDetection }
|
|
24
|
+
} }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function validateOpenAITokenRequest(value, defaultModel = OPENAI_DEFAULT_MODEL) {
|
|
28
|
+
if (!isRecord(value)) throw new TypeError("The token request body must be a JSON object.");
|
|
29
|
+
const model = value["model"] ?? defaultModel;
|
|
30
|
+
if (typeof model !== "string" || model.trim().length === 0) throw new TypeError("model must be a non-empty string.");
|
|
31
|
+
const language = normalizeLanguage(value["language"]);
|
|
32
|
+
if (language !== void 0 && (language.trim().length === 0 || language.length > 64)) throw new TypeError("language must be a non-empty string.");
|
|
33
|
+
const vocabulary = validateVocabulary(value["vocabulary"]);
|
|
34
|
+
const endpointing = validateEndpointing(value["endpointing"]);
|
|
35
|
+
if (model.startsWith("gpt-live-transcribe") && endpointing !== void 0 && endpointing !== false) throw unsupportedFeature("gpt-live-transcribe does not support server turn detection. Use endpointing: false for one manually committed segment, or choose gpt-transcribe for phrase boundaries.");
|
|
36
|
+
return {
|
|
37
|
+
model,
|
|
38
|
+
...language === void 0 ? {} : { language },
|
|
39
|
+
...vocabulary === void 0 ? {} : { vocabulary },
|
|
40
|
+
...endpointing === void 0 ? {} : { endpointing }
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function normalizeLanguage(value) {
|
|
44
|
+
if (value === void 0) return;
|
|
45
|
+
if (typeof value !== "string") throw new TypeError("language must be a valid BCP 47 language tag.");
|
|
46
|
+
let primaryLanguage;
|
|
47
|
+
try {
|
|
48
|
+
primaryLanguage = new Intl.Locale(value).language;
|
|
49
|
+
} catch {
|
|
50
|
+
throw new TypeError("language must be a valid BCP 47 language tag.");
|
|
51
|
+
}
|
|
52
|
+
if (!/^[a-z]{2}$/u.test(primaryLanguage)) throw unsupportedFeature("OpenAI requires a language tag with an ISO 639-1 primary language subtag.");
|
|
53
|
+
return primaryLanguage;
|
|
54
|
+
}
|
|
55
|
+
function createTurnDetection(endpointing, liveModel) {
|
|
56
|
+
if (endpointing === false || liveModel) return null;
|
|
57
|
+
return {
|
|
58
|
+
type: "server_vad",
|
|
59
|
+
silence_duration_ms: endpointing?.silenceMs ?? 500
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function validateVocabulary(value) {
|
|
63
|
+
if (value === void 0) return;
|
|
64
|
+
if (!Array.isArray(value)) throw new TypeError("vocabulary must be an array of strings.");
|
|
65
|
+
const vocabulary = value.map((term) => {
|
|
66
|
+
if (typeof term !== "string" || term.trim().length === 0 || term !== term.trim() || /[<>\r\n]/u.test(term)) throw new TypeError("Vocabulary terms must be trimmed strings without angle brackets or line breaks.");
|
|
67
|
+
return term;
|
|
68
|
+
});
|
|
69
|
+
if (vocabulary.length > 100) throw unsupportedFeature("OpenAI vocabulary supports at most 100 terms.");
|
|
70
|
+
if (vocabulary.some((term) => term.length > 200)) throw unsupportedFeature("OpenAI vocabulary terms support at most 200 characters.");
|
|
71
|
+
return Object.freeze(vocabulary);
|
|
72
|
+
}
|
|
73
|
+
function validateEndpointing(value) {
|
|
74
|
+
if (value === void 0 || value === false) return value;
|
|
75
|
+
if (!isRecord(value) || Object.keys(value).some((key) => key !== "silenceMs") || !Number.isInteger(value["silenceMs"]) || value["silenceMs"] <= 0) throw new TypeError("endpointing must be false or an object with a positive integer silenceMs.");
|
|
76
|
+
return { silenceMs: value["silenceMs"] };
|
|
77
|
+
}
|
|
78
|
+
function isRecord(value) {
|
|
79
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
80
|
+
}
|
|
81
|
+
function unsupportedFeature(message) {
|
|
82
|
+
return new _voiceinput_provider.VoiceInputError({
|
|
83
|
+
code: "unsupported-feature",
|
|
84
|
+
message,
|
|
85
|
+
provider: "openai"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
Object.defineProperty(exports, "OPENAI_DEFAULT_MODEL", {
|
|
90
|
+
enumerable: true,
|
|
91
|
+
get: function() {
|
|
92
|
+
return OPENAI_DEFAULT_MODEL;
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
Object.defineProperty(exports, "OPENAI_SAMPLE_RATE", {
|
|
96
|
+
enumerable: true,
|
|
97
|
+
get: function() {
|
|
98
|
+
return OPENAI_SAMPLE_RATE;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
Object.defineProperty(exports, "createOpenAITranscriptionSession", {
|
|
102
|
+
enumerable: true,
|
|
103
|
+
get: function() {
|
|
104
|
+
return createOpenAITranscriptionSession;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
Object.defineProperty(exports, "validateOpenAITokenRequest", {
|
|
108
|
+
enumerable: true,
|
|
109
|
+
get: function() {
|
|
110
|
+
return validateOpenAITokenRequest;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
//# sourceMappingURL=session-config-CmoDDiY6.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-config-CmoDDiY6.cjs","names":["VoiceInputError"],"sources":["../src/session-config.ts"],"sourcesContent":["import {\n VoiceInputError,\n type VoiceEndpointingOptions,\n type VoiceTranscriptionOptions,\n} from \"@voiceinput/provider\";\n\nexport const OPENAI_DEFAULT_MODEL = \"gpt-transcribe\";\nexport const OPENAI_SAMPLE_RATE = 24_000;\n\nexport interface OpenAITokenRequest extends VoiceTranscriptionOptions {\n model: string;\n}\n\nexport function createOpenAITranscriptionSession(\n options: OpenAITokenRequest,\n): Record<string, unknown> {\n const vocabulary = options.vocabulary ?? [];\n const liveModel = options.model.startsWith(\"gpt-live-transcribe\");\n const prompt =\n liveModel || vocabulary.length === 0\n ? undefined\n : `Expected vocabulary: ${vocabulary.join(\", \")}.`;\n const turnDetection = createTurnDetection(options.endpointing, liveModel);\n\n return {\n type: \"transcription\",\n audio: {\n input: {\n format: {\n type: \"audio/pcm\",\n rate: OPENAI_SAMPLE_RATE,\n },\n transcription: {\n model: options.model,\n ...(prompt === undefined ? {} : { prompt }),\n ...(liveModel && vocabulary.length > 0\n ? { keywords: vocabulary }\n : {}),\n ...(options.language === undefined\n ? {}\n : liveModel\n ? { languages: [options.language] }\n : { language: options.language }),\n },\n ...(turnDetection === undefined\n ? {}\n : { turn_detection: turnDetection }),\n },\n },\n };\n}\n\nexport function validateOpenAITokenRequest(\n value: unknown,\n defaultModel = OPENAI_DEFAULT_MODEL,\n): OpenAITokenRequest {\n if (!isRecord(value)) {\n throw new TypeError(\"The token request body must be a JSON object.\");\n }\n\n const model = value[\"model\"] ?? defaultModel;\n if (typeof model !== \"string\" || model.trim().length === 0) {\n throw new TypeError(\"model must be a non-empty string.\");\n }\n\n const language = normalizeLanguage(value[\"language\"]);\n if (\n language !== undefined &&\n (language.trim().length === 0 || language.length > 64)\n ) {\n throw new TypeError(\"language must be a non-empty string.\");\n }\n\n const vocabulary = validateVocabulary(value[\"vocabulary\"]);\n const endpointing = validateEndpointing(value[\"endpointing\"]);\n if (\n model.startsWith(\"gpt-live-transcribe\") &&\n endpointing !== undefined &&\n endpointing !== false\n ) {\n throw unsupportedFeature(\n \"gpt-live-transcribe does not support server turn detection. Use endpointing: false for one manually committed segment, or choose gpt-transcribe for phrase boundaries.\",\n );\n }\n\n return {\n model,\n ...(language === undefined ? {} : { language }),\n ...(vocabulary === undefined ? {} : { vocabulary }),\n ...(endpointing === undefined ? {} : { endpointing }),\n };\n}\n\nfunction normalizeLanguage(value: unknown): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (typeof value !== \"string\") {\n throw new TypeError(\"language must be a valid BCP 47 language tag.\");\n }\n let primaryLanguage: string;\n try {\n primaryLanguage = new Intl.Locale(value).language;\n } catch {\n throw new TypeError(\"language must be a valid BCP 47 language tag.\");\n }\n if (!/^[a-z]{2}$/u.test(primaryLanguage)) {\n throw unsupportedFeature(\n \"OpenAI requires a language tag with an ISO 639-1 primary language subtag.\",\n );\n }\n return primaryLanguage;\n}\n\nfunction createTurnDetection(\n endpointing: false | VoiceEndpointingOptions | undefined,\n liveModel: boolean,\n): Record<string, unknown> | null {\n if (endpointing === false || liveModel) {\n return null;\n }\n return {\n type: \"server_vad\",\n silence_duration_ms: endpointing?.silenceMs ?? 500,\n };\n}\n\nfunction validateVocabulary(value: unknown): 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.trim().length === 0 ||\n term !== term.trim() ||\n /[<>\\r\\n]/u.test(term)\n ) {\n throw new TypeError(\n \"Vocabulary terms must be trimmed strings without angle brackets or line breaks.\",\n );\n }\n return term;\n });\n if (vocabulary.length > 100) {\n throw unsupportedFeature(\"OpenAI vocabulary supports at most 100 terms.\");\n }\n if (vocabulary.some((term) => term.length > 200)) {\n throw unsupportedFeature(\n \"OpenAI vocabulary terms support at most 200 characters.\",\n );\n }\n return Object.freeze(vocabulary);\n}\n\nfunction validateEndpointing(\n value: unknown,\n): false | VoiceEndpointingOptions | undefined {\n if (value === undefined || value === false) {\n return value;\n }\n if (\n !isRecord(value) ||\n Object.keys(value).some((key) => key !== \"silenceMs\") ||\n !Number.isInteger(value[\"silenceMs\"]) ||\n (value[\"silenceMs\"] as number) <= 0\n ) {\n throw new TypeError(\n \"endpointing must be false or an object with a positive integer silenceMs.\",\n );\n }\n return { silenceMs: value[\"silenceMs\"] as number };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction unsupportedFeature(message: string): VoiceInputError {\n return new VoiceInputError({\n code: \"unsupported-feature\",\n message,\n provider: \"openai\",\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { VoiceInputError } from "@voiceinput/provider";
|
|
2
|
+
//#region src/session-config.ts
|
|
3
|
+
const OPENAI_DEFAULT_MODEL = "gpt-transcribe";
|
|
4
|
+
const OPENAI_SAMPLE_RATE = 24e3;
|
|
5
|
+
function createOpenAITranscriptionSession(options) {
|
|
6
|
+
const vocabulary = options.vocabulary ?? [];
|
|
7
|
+
const liveModel = options.model.startsWith("gpt-live-transcribe");
|
|
8
|
+
const prompt = liveModel || vocabulary.length === 0 ? void 0 : `Expected vocabulary: ${vocabulary.join(", ")}.`;
|
|
9
|
+
const turnDetection = createTurnDetection(options.endpointing, liveModel);
|
|
10
|
+
return {
|
|
11
|
+
type: "transcription",
|
|
12
|
+
audio: { input: {
|
|
13
|
+
format: {
|
|
14
|
+
type: "audio/pcm",
|
|
15
|
+
rate: OPENAI_SAMPLE_RATE
|
|
16
|
+
},
|
|
17
|
+
transcription: {
|
|
18
|
+
model: options.model,
|
|
19
|
+
...prompt === void 0 ? {} : { prompt },
|
|
20
|
+
...liveModel && vocabulary.length > 0 ? { keywords: vocabulary } : {},
|
|
21
|
+
...options.language === void 0 ? {} : liveModel ? { languages: [options.language] } : { language: options.language }
|
|
22
|
+
},
|
|
23
|
+
...turnDetection === void 0 ? {} : { turn_detection: turnDetection }
|
|
24
|
+
} }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function validateOpenAITokenRequest(value, defaultModel = OPENAI_DEFAULT_MODEL) {
|
|
28
|
+
if (!isRecord(value)) throw new TypeError("The token request body must be a JSON object.");
|
|
29
|
+
const model = value["model"] ?? defaultModel;
|
|
30
|
+
if (typeof model !== "string" || model.trim().length === 0) throw new TypeError("model must be a non-empty string.");
|
|
31
|
+
const language = normalizeLanguage(value["language"]);
|
|
32
|
+
if (language !== void 0 && (language.trim().length === 0 || language.length > 64)) throw new TypeError("language must be a non-empty string.");
|
|
33
|
+
const vocabulary = validateVocabulary(value["vocabulary"]);
|
|
34
|
+
const endpointing = validateEndpointing(value["endpointing"]);
|
|
35
|
+
if (model.startsWith("gpt-live-transcribe") && endpointing !== void 0 && endpointing !== false) throw unsupportedFeature("gpt-live-transcribe does not support server turn detection. Use endpointing: false for one manually committed segment, or choose gpt-transcribe for phrase boundaries.");
|
|
36
|
+
return {
|
|
37
|
+
model,
|
|
38
|
+
...language === void 0 ? {} : { language },
|
|
39
|
+
...vocabulary === void 0 ? {} : { vocabulary },
|
|
40
|
+
...endpointing === void 0 ? {} : { endpointing }
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function normalizeLanguage(value) {
|
|
44
|
+
if (value === void 0) return;
|
|
45
|
+
if (typeof value !== "string") throw new TypeError("language must be a valid BCP 47 language tag.");
|
|
46
|
+
let primaryLanguage;
|
|
47
|
+
try {
|
|
48
|
+
primaryLanguage = new Intl.Locale(value).language;
|
|
49
|
+
} catch {
|
|
50
|
+
throw new TypeError("language must be a valid BCP 47 language tag.");
|
|
51
|
+
}
|
|
52
|
+
if (!/^[a-z]{2}$/u.test(primaryLanguage)) throw unsupportedFeature("OpenAI requires a language tag with an ISO 639-1 primary language subtag.");
|
|
53
|
+
return primaryLanguage;
|
|
54
|
+
}
|
|
55
|
+
function createTurnDetection(endpointing, liveModel) {
|
|
56
|
+
if (endpointing === false || liveModel) return null;
|
|
57
|
+
return {
|
|
58
|
+
type: "server_vad",
|
|
59
|
+
silence_duration_ms: endpointing?.silenceMs ?? 500
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function validateVocabulary(value) {
|
|
63
|
+
if (value === void 0) return;
|
|
64
|
+
if (!Array.isArray(value)) throw new TypeError("vocabulary must be an array of strings.");
|
|
65
|
+
const vocabulary = value.map((term) => {
|
|
66
|
+
if (typeof term !== "string" || term.trim().length === 0 || term !== term.trim() || /[<>\r\n]/u.test(term)) throw new TypeError("Vocabulary terms must be trimmed strings without angle brackets or line breaks.");
|
|
67
|
+
return term;
|
|
68
|
+
});
|
|
69
|
+
if (vocabulary.length > 100) throw unsupportedFeature("OpenAI vocabulary supports at most 100 terms.");
|
|
70
|
+
if (vocabulary.some((term) => term.length > 200)) throw unsupportedFeature("OpenAI vocabulary terms support at most 200 characters.");
|
|
71
|
+
return Object.freeze(vocabulary);
|
|
72
|
+
}
|
|
73
|
+
function validateEndpointing(value) {
|
|
74
|
+
if (value === void 0 || value === false) return value;
|
|
75
|
+
if (!isRecord(value) || Object.keys(value).some((key) => key !== "silenceMs") || !Number.isInteger(value["silenceMs"]) || value["silenceMs"] <= 0) throw new TypeError("endpointing must be false or an object with a positive integer silenceMs.");
|
|
76
|
+
return { silenceMs: value["silenceMs"] };
|
|
77
|
+
}
|
|
78
|
+
function isRecord(value) {
|
|
79
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
80
|
+
}
|
|
81
|
+
function unsupportedFeature(message) {
|
|
82
|
+
return new VoiceInputError({
|
|
83
|
+
code: "unsupported-feature",
|
|
84
|
+
message,
|
|
85
|
+
provider: "openai"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { validateOpenAITokenRequest as i, OPENAI_SAMPLE_RATE as n, createOpenAITranscriptionSession as r, OPENAI_DEFAULT_MODEL as t };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=session-config-CuDb4m9Q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-config-CuDb4m9Q.js","names":[],"sources":["../src/session-config.ts"],"sourcesContent":["import {\n VoiceInputError,\n type VoiceEndpointingOptions,\n type VoiceTranscriptionOptions,\n} from \"@voiceinput/provider\";\n\nexport const OPENAI_DEFAULT_MODEL = \"gpt-transcribe\";\nexport const OPENAI_SAMPLE_RATE = 24_000;\n\nexport interface OpenAITokenRequest extends VoiceTranscriptionOptions {\n model: string;\n}\n\nexport function createOpenAITranscriptionSession(\n options: OpenAITokenRequest,\n): Record<string, unknown> {\n const vocabulary = options.vocabulary ?? [];\n const liveModel = options.model.startsWith(\"gpt-live-transcribe\");\n const prompt =\n liveModel || vocabulary.length === 0\n ? undefined\n : `Expected vocabulary: ${vocabulary.join(\", \")}.`;\n const turnDetection = createTurnDetection(options.endpointing, liveModel);\n\n return {\n type: \"transcription\",\n audio: {\n input: {\n format: {\n type: \"audio/pcm\",\n rate: OPENAI_SAMPLE_RATE,\n },\n transcription: {\n model: options.model,\n ...(prompt === undefined ? {} : { prompt }),\n ...(liveModel && vocabulary.length > 0\n ? { keywords: vocabulary }\n : {}),\n ...(options.language === undefined\n ? {}\n : liveModel\n ? { languages: [options.language] }\n : { language: options.language }),\n },\n ...(turnDetection === undefined\n ? {}\n : { turn_detection: turnDetection }),\n },\n },\n };\n}\n\nexport function validateOpenAITokenRequest(\n value: unknown,\n defaultModel = OPENAI_DEFAULT_MODEL,\n): OpenAITokenRequest {\n if (!isRecord(value)) {\n throw new TypeError(\"The token request body must be a JSON object.\");\n }\n\n const model = value[\"model\"] ?? defaultModel;\n if (typeof model !== \"string\" || model.trim().length === 0) {\n throw new TypeError(\"model must be a non-empty string.\");\n }\n\n const language = normalizeLanguage(value[\"language\"]);\n if (\n language !== undefined &&\n (language.trim().length === 0 || language.length > 64)\n ) {\n throw new TypeError(\"language must be a non-empty string.\");\n }\n\n const vocabulary = validateVocabulary(value[\"vocabulary\"]);\n const endpointing = validateEndpointing(value[\"endpointing\"]);\n if (\n model.startsWith(\"gpt-live-transcribe\") &&\n endpointing !== undefined &&\n endpointing !== false\n ) {\n throw unsupportedFeature(\n \"gpt-live-transcribe does not support server turn detection. Use endpointing: false for one manually committed segment, or choose gpt-transcribe for phrase boundaries.\",\n );\n }\n\n return {\n model,\n ...(language === undefined ? {} : { language }),\n ...(vocabulary === undefined ? {} : { vocabulary }),\n ...(endpointing === undefined ? {} : { endpointing }),\n };\n}\n\nfunction normalizeLanguage(value: unknown): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (typeof value !== \"string\") {\n throw new TypeError(\"language must be a valid BCP 47 language tag.\");\n }\n let primaryLanguage: string;\n try {\n primaryLanguage = new Intl.Locale(value).language;\n } catch {\n throw new TypeError(\"language must be a valid BCP 47 language tag.\");\n }\n if (!/^[a-z]{2}$/u.test(primaryLanguage)) {\n throw unsupportedFeature(\n \"OpenAI requires a language tag with an ISO 639-1 primary language subtag.\",\n );\n }\n return primaryLanguage;\n}\n\nfunction createTurnDetection(\n endpointing: false | VoiceEndpointingOptions | undefined,\n liveModel: boolean,\n): Record<string, unknown> | null {\n if (endpointing === false || liveModel) {\n return null;\n }\n return {\n type: \"server_vad\",\n silence_duration_ms: endpointing?.silenceMs ?? 500,\n };\n}\n\nfunction validateVocabulary(value: unknown): 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.trim().length === 0 ||\n term !== term.trim() ||\n /[<>\\r\\n]/u.test(term)\n ) {\n throw new TypeError(\n \"Vocabulary terms must be trimmed strings without angle brackets or line breaks.\",\n );\n }\n return term;\n });\n if (vocabulary.length > 100) {\n throw unsupportedFeature(\"OpenAI vocabulary supports at most 100 terms.\");\n }\n if (vocabulary.some((term) => term.length > 200)) {\n throw unsupportedFeature(\n \"OpenAI vocabulary terms support at most 200 characters.\",\n );\n }\n return Object.freeze(vocabulary);\n}\n\nfunction validateEndpointing(\n value: unknown,\n): false | VoiceEndpointingOptions | undefined {\n if (value === undefined || value === false) {\n return value;\n }\n if (\n !isRecord(value) ||\n Object.keys(value).some((key) => key !== \"silenceMs\") ||\n !Number.isInteger(value[\"silenceMs\"]) ||\n (value[\"silenceMs\"] as number) <= 0\n ) {\n throw new TypeError(\n \"endpointing must be false or an object with a positive integer silenceMs.\",\n );\n }\n return { silenceMs: value[\"silenceMs\"] as number };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction unsupportedFeature(message: string): VoiceInputError {\n return new VoiceInputError({\n code: \"unsupported-feature\",\n message,\n provider: \"openai\",\n });\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voiceinput/openai",
|
|
3
|
+
"version": "0.1.0-beta.1",
|
|
4
|
+
"description": "OpenAI Realtime transcription adapter and secure token handler for VoiceInput.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"voice-input",
|
|
7
|
+
"speech-to-text",
|
|
8
|
+
"transcription",
|
|
9
|
+
"openai"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://voiceinput.dev/docs/providers/openai",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/VoiceInput/voiceinput/issues"
|
|
14
|
+
},
|
|
15
|
+
"author": "VoiceInput contributors",
|
|
16
|
+
"maintainers": [
|
|
17
|
+
"Hirad Arshadi (https://github.com/hiradary)"
|
|
18
|
+
],
|
|
19
|
+
"type": "module",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/VoiceInput/voiceinput.git",
|
|
27
|
+
"directory": "packages/openai"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"sideEffects": false,
|
|
33
|
+
"main": "./dist/index.cjs",
|
|
34
|
+
"module": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"import": {
|
|
39
|
+
"types": "./dist/index.d.ts",
|
|
40
|
+
"default": "./dist/index.js"
|
|
41
|
+
},
|
|
42
|
+
"require": {
|
|
43
|
+
"types": "./dist/index.d.cts",
|
|
44
|
+
"default": "./dist/index.cjs"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"./server": {
|
|
48
|
+
"browser": null,
|
|
49
|
+
"import": {
|
|
50
|
+
"types": "./dist/server.d.ts",
|
|
51
|
+
"default": "./dist/server.js"
|
|
52
|
+
},
|
|
53
|
+
"require": {
|
|
54
|
+
"types": "./dist/server.d.cts",
|
|
55
|
+
"default": "./dist/server.cjs"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@voiceinput/provider": "0.1.0-beta.1"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@voiceinput/core": "0.1.0-beta.1"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsdown src/index.ts src/server.ts",
|
|
70
|
+
"dev": "tsdown src/index.ts src/server.ts --watch --no-clean",
|
|
71
|
+
"pack": "pnpm pack --pack-destination .",
|
|
72
|
+
"test": "vitest run",
|
|
73
|
+
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
74
|
+
"lint": "oxlint --config ../../.oxlintrc.json .",
|
|
75
|
+
"format:check": "prettier --check ."
|
|
76
|
+
}
|
|
77
|
+
}
|