@fonoster/community 0.0.0-stage → 0.27.0

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.
@@ -0,0 +1,29 @@
1
+ import { Think, ThinkProvider } from "./llm";
2
+ import { SttAdapter, TtsAdapter } from "./speech";
3
+ /** How a speech adapter is reached: key, endpoint override, provider model, timeout. */
4
+ export type SpeechAdapterOptions = {
5
+ apiKey?: string;
6
+ url?: string;
7
+ model?: string;
8
+ timeoutMs?: number;
9
+ };
10
+ export type LlmAdapterFactory = {
11
+ /** The vendor's endpoint when the operator configures none. */
12
+ defaultEndpoint?: string;
13
+ create(provider: ThinkProvider, model: string, settings: {
14
+ temperature: number;
15
+ maxTokens: number;
16
+ jsonMode?: boolean;
17
+ }): Think;
18
+ };
19
+ export type AdapterFactories = {
20
+ /** Works without a credential (fake, local models). */
21
+ keyless?: boolean;
22
+ /** Undefined when the options are not enough to reach the vendor. */
23
+ tts?(options: SpeechAdapterOptions): TtsAdapter | undefined;
24
+ stt?(options: SpeechAdapterOptions): SttAdapter | undefined;
25
+ llm?: LlmAdapterFactory;
26
+ };
27
+ export declare const ADAPTERS: Readonly<Record<string, AdapterFactories>>;
28
+ /** The factories for a catalog adapter id, or undefined when this version has none. */
29
+ export declare function adapterFor(id: string): AdapterFactories | undefined;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ADAPTERS = void 0;
4
+ exports.adapterFor = adapterFor;
5
+ /**
6
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
7
+ * http://github.com/fonoster/fonoster
8
+ *
9
+ * This file is part of Fonoster
10
+ *
11
+ * Licensed under the MIT License (the "License");
12
+ * you may not use this file except in compliance with
13
+ * the License. You may obtain a copy of the License at
14
+ *
15
+ * https://opensource.org/licenses/MIT
16
+ *
17
+ * Unless required by applicable law or agreed to in writing, software
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ * See the License for the specific language governing permissions and
21
+ * limitations under the License.
22
+ */
23
+ const deepgram_1 = require("./deepgram");
24
+ const elevenlabs_1 = require("./elevenlabs");
25
+ const fake_1 = require("./fake");
26
+ const openai_1 = require("./openai");
27
+ function openAiCompatible(adapter) {
28
+ return {
29
+ defaultEndpoint: openai_1.OPENAI_COMPATIBLE[adapter],
30
+ create: (provider, model, settings) => new openai_1.OpenAiCompatibleThink(provider, model, settings)
31
+ };
32
+ }
33
+ exports.ADAPTERS = {
34
+ deepgram: {
35
+ tts: (o) => {
36
+ var _a;
37
+ return new deepgram_1.DeepgramTtsAdapter({
38
+ apiKey: (_a = o.apiKey) !== null && _a !== void 0 ? _a : "",
39
+ baseUrl: o.url,
40
+ timeoutMs: o.timeoutMs
41
+ });
42
+ },
43
+ stt: (o) => {
44
+ var _a;
45
+ return new deepgram_1.DeepgramSttAdapter({
46
+ apiKey: (_a = o.apiKey) !== null && _a !== void 0 ? _a : "",
47
+ baseUrl: o.url,
48
+ sttModel: o.model
49
+ });
50
+ }
51
+ },
52
+ elevenlabs: {
53
+ tts: (o) => {
54
+ var _a;
55
+ return new elevenlabs_1.ElevenLabsTtsAdapter({
56
+ apiKey: (_a = o.apiKey) !== null && _a !== void 0 ? _a : "",
57
+ baseUrl: o.url,
58
+ model: o.model,
59
+ timeoutMs: o.timeoutMs
60
+ });
61
+ }
62
+ },
63
+ openai: { llm: openAiCompatible("openai") },
64
+ groq: { llm: openAiCompatible("groq") },
65
+ ollama: { keyless: true, llm: openAiCompatible("ollama") },
66
+ fake: {
67
+ keyless: true,
68
+ tts: (o) => {
69
+ var _a;
70
+ return o.url
71
+ ? new fake_1.FakeTtsAdapter({ url: o.url, timeoutMs: (_a = o.timeoutMs) !== null && _a !== void 0 ? _a : 3000 })
72
+ : undefined;
73
+ },
74
+ stt: (o) => (o.url ? new fake_1.FakeSttAdapter({ url: o.url }) : undefined),
75
+ llm: openAiCompatible("fake")
76
+ }
77
+ };
78
+ /** The factories for a catalog adapter id, or undefined when this version has none. */
79
+ function adapterFor(id) {
80
+ return Object.prototype.hasOwnProperty.call(exports.ADAPTERS, id)
81
+ ? exports.ADAPTERS[id]
82
+ : undefined;
83
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
3
+ * http://github.com/fonoster/fonoster
4
+ *
5
+ * This file is part of Fonoster
6
+ *
7
+ * Licensed under the MIT License (the "License");
8
+ * you may not use this file except in compliance with
9
+ * the License. You may obtain a copy of the License at
10
+ *
11
+ * https://opensource.org/licenses/MIT
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ import { EventEmitter } from "events";
20
+ import { SttAdapter, SttStream, Synthesis, TtsAdapter } from "../speech";
21
+ export type DeepgramOptions = {
22
+ apiKey: string;
23
+ baseUrl?: string;
24
+ sttModel?: string;
25
+ timeoutMs?: number;
26
+ };
27
+ export declare class DeepgramTtsAdapter implements TtsAdapter {
28
+ private readonly options;
29
+ constructor(options: DeepgramOptions);
30
+ synthesize(request: {
31
+ text: string;
32
+ voice: string;
33
+ language: string;
34
+ }, signal: AbortSignal): Promise<Synthesis>;
35
+ }
36
+ /** Deepgram's keyterm prompting works on Nova-3 and Flux; older models take `keywords`. */
37
+ export declare function hintParam(model: string): "keyterm" | "keywords";
38
+ export declare class DeepgramSttStream extends EventEmitter implements SttStream {
39
+ private readonly options;
40
+ private language;
41
+ private hints;
42
+ private readonly model;
43
+ private socket;
44
+ private queue;
45
+ private keepalive?;
46
+ private closed;
47
+ private finishing;
48
+ private failures;
49
+ private retry?;
50
+ constructor(options: DeepgramOptions, language: string, hints?: string[], model?: string);
51
+ private connect;
52
+ private onMessage;
53
+ write(frame: Buffer): void;
54
+ /**
55
+ * Deepgram cannot change language or keyterms mid-stream, so the stream reconnects. The old
56
+ * socket is finalized and drained, so words spoken just before the change are not lost.
57
+ */
58
+ update(options: {
59
+ language?: string;
60
+ hints?: string[];
61
+ }): void;
62
+ /**
63
+ * Ends the audio and waits (up to timeoutMs) for Deepgram to send its last results and close
64
+ * the socket, then closes the stream. Deepgram works through audio at about real-time pace even
65
+ * when it arrives faster, so a fixed wait followed by close() can drop every final (PR5, VO-B1).
66
+ */
67
+ finish(timeoutMs?: number): Promise<void>;
68
+ close(): void;
69
+ private retire;
70
+ }
71
+ export declare class DeepgramSttAdapter implements SttAdapter {
72
+ private readonly options;
73
+ constructor(options: DeepgramOptions);
74
+ open(options: {
75
+ language: string;
76
+ hints?: string[];
77
+ model?: string;
78
+ }): DeepgramSttStream;
79
+ }
@@ -0,0 +1,268 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.DeepgramSttAdapter = exports.DeepgramSttStream = exports.DeepgramTtsAdapter = void 0;
16
+ exports.hintParam = hintParam;
17
+ /**
18
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
19
+ * http://github.com/fonoster/fonoster
20
+ *
21
+ * This file is part of Fonoster
22
+ *
23
+ * Licensed under the MIT License (the "License");
24
+ * you may not use this file except in compliance with
25
+ * the License. You may obtain a copy of the License at
26
+ *
27
+ * https://opensource.org/licenses/MIT
28
+ *
29
+ * Unless required by applicable law or agreed to in writing, software
30
+ * distributed under the License is distributed on an "AS IS" BASIS,
31
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32
+ * See the License for the specific language governing permissions and
33
+ * limitations under the License.
34
+ */
35
+ const events_1 = require("events");
36
+ const url_1 = require("url");
37
+ const ws_1 = __importDefault(require("ws"));
38
+ const http_1 = require("../http");
39
+ const speech_1 = require("../speech");
40
+ const API = "https://api.deepgram.com";
41
+ class DeepgramTtsAdapter {
42
+ constructor(options) {
43
+ this.options = options;
44
+ }
45
+ synthesize(request, signal) {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ var _a, _b;
48
+ // The voice is a Deepgram Aura model name (catalog id "deepgram/aura-2-thalia-en").
49
+ const query = new url_1.URLSearchParams({
50
+ model: request.voice,
51
+ encoding: "linear16",
52
+ sample_rate: "16000",
53
+ container: "none"
54
+ });
55
+ const audio = yield (0, http_1.streamingPost)(`${(_a = this.options.baseUrl) !== null && _a !== void 0 ? _a : API}/v1/speak?${query}`, {
56
+ headers: {
57
+ authorization: `Token ${this.options.apiKey}`,
58
+ "content-type": "application/json"
59
+ },
60
+ body: JSON.stringify({ text: request.text })
61
+ }, signal, (_b = this.options.timeoutMs) !== null && _b !== void 0 ? _b : 3000, "Deepgram TTS");
62
+ return { audio };
63
+ });
64
+ }
65
+ }
66
+ exports.DeepgramTtsAdapter = DeepgramTtsAdapter;
67
+ /** Deepgram's keyterm prompting works on Nova-3 and Flux; older models take `keywords`. */
68
+ function hintParam(model) {
69
+ return /^(nova-3|flux)/.test(model) ? "keyterm" : "keywords";
70
+ }
71
+ /** Frames kept while (re)connecting: 5 s of 20 ms frames. */
72
+ const MAX_QUEUED_FRAMES = 250;
73
+ /** How long a replaced socket may keep delivering its last transcripts (VO-B1). */
74
+ const DRAIN_MS = 2000;
75
+ class DeepgramSttStream extends events_1.EventEmitter {
76
+ constructor(options, language, hints, model) {
77
+ var _a;
78
+ if (hints === void 0) { hints = []; }
79
+ if (model === void 0) { model = (_a = options.sttModel) !== null && _a !== void 0 ? _a : "nova-3"; }
80
+ super();
81
+ this.options = options;
82
+ this.language = language;
83
+ this.hints = hints;
84
+ this.model = model;
85
+ this.queue = [];
86
+ this.closed = false;
87
+ this.finishing = false;
88
+ this.failures = 0;
89
+ this.connect();
90
+ }
91
+ connect() {
92
+ var _a;
93
+ const query = new url_1.URLSearchParams({
94
+ model: this.model,
95
+ encoding: "linear16",
96
+ sample_rate: "16000",
97
+ channels: "1",
98
+ interim_results: "true",
99
+ smart_format: "true",
100
+ language: this.language
101
+ });
102
+ const param = hintParam(this.model);
103
+ for (const h of this.hints)
104
+ query.append(param, h);
105
+ const base = ((_a = this.options.baseUrl) !== null && _a !== void 0 ? _a : API).replace(/^http/, "ws");
106
+ const socket = new ws_1.default(`${base}/v1/listen?${query}`, {
107
+ headers: { authorization: `Token ${this.options.apiKey}` }
108
+ });
109
+ this.socket = socket;
110
+ let rejected = false;
111
+ socket.on("open", () => {
112
+ for (const f of this.queue)
113
+ socket.send(f);
114
+ this.queue = [];
115
+ clearInterval(this.keepalive);
116
+ this.keepalive = setInterval(() => socket.readyState === ws_1.default.OPEN &&
117
+ socket.send(JSON.stringify({ type: "KeepAlive" })), 5000);
118
+ });
119
+ socket.on("message", (raw) => this.onMessage(raw.toString()));
120
+ // A rejected handshake (bad key, unknown model or language) is not retried.
121
+ socket.on("unexpected-response", (req, res) => {
122
+ rejected = true;
123
+ req.destroy();
124
+ if (this.closed || socket !== this.socket)
125
+ return;
126
+ this.emit("error", new speech_1.SpeechError(res.statusCode === 400 ? "bad_message" : "provider_unavailable", `Deepgram STT answered HTTP ${res.statusCode}`));
127
+ });
128
+ socket.on("error", () => undefined);
129
+ // Deepgram may close a live stream (NET-0001 after 10 s without audio, 1011 on its side). The
130
+ // old adapter went quietly deaf then; now it reconnects, keeping frames meanwhile (VO-B1).
131
+ socket.on("close", (code, reason) => {
132
+ if (socket !== this.socket || this.closed || this.finishing || rejected)
133
+ return;
134
+ clearInterval(this.keepalive);
135
+ if (code === 1008) {
136
+ this.emit("error", new speech_1.SpeechError("bad_message", `Deepgram STT rejected the stream: ${reason.toString() || code}`));
137
+ return;
138
+ }
139
+ if (++this.failures > 3) {
140
+ this.emit("error", new speech_1.SpeechError("provider_unavailable", `Deepgram STT closed the stream (${code} ${reason.toString()})`.trim()));
141
+ return;
142
+ }
143
+ this.retry = setTimeout(() => !this.closed && this.connect(), 250 * this.failures);
144
+ });
145
+ }
146
+ onMessage(text) {
147
+ var _a, _b, _c;
148
+ let msg;
149
+ try {
150
+ msg = JSON.parse(text);
151
+ }
152
+ catch (_d) {
153
+ return;
154
+ }
155
+ if (this.closed || msg.type !== "Results")
156
+ return;
157
+ this.failures = 0;
158
+ const alt = (_b = (_a = msg.channel) === null || _a === void 0 ? void 0 : _a.alternatives) === null || _b === void 0 ? void 0 : _b[0];
159
+ if (!(alt === null || alt === void 0 ? void 0 : alt.transcript))
160
+ return;
161
+ const t = {
162
+ text: alt.transcript,
163
+ final: msg.is_final === true,
164
+ language: this.language,
165
+ confidence: (_c = alt.confidence) !== null && _c !== void 0 ? _c : 0
166
+ };
167
+ // Word timings (catalog feature `word-timings`), in ms from the start of this vendor stream.
168
+ const ms = (sec) => Math.round(Number(sec) * 1000);
169
+ if (typeof msg.start === "number" && typeof msg.duration === "number") {
170
+ t.startMs = ms(msg.start);
171
+ t.endMs = ms(msg.start + msg.duration);
172
+ }
173
+ if (Array.isArray(alt.words) && alt.words.length)
174
+ t.words = alt.words.map((w) => {
175
+ var _a;
176
+ return ({
177
+ word: (_a = w.punctuated_word) !== null && _a !== void 0 ? _a : w.word,
178
+ startMs: ms(w.start),
179
+ endMs: ms(w.end)
180
+ });
181
+ });
182
+ this.emit("transcript", t);
183
+ }
184
+ write(frame) {
185
+ if (this.closed)
186
+ return;
187
+ if (this.socket.readyState === ws_1.default.OPEN)
188
+ this.socket.send(frame);
189
+ else {
190
+ this.queue.push(frame);
191
+ if (this.queue.length > MAX_QUEUED_FRAMES)
192
+ this.queue.shift();
193
+ }
194
+ }
195
+ /**
196
+ * Deepgram cannot change language or keyterms mid-stream, so the stream reconnects. The old
197
+ * socket is finalized and drained, so words spoken just before the change are not lost.
198
+ */
199
+ update(options) {
200
+ var _a, _b;
201
+ if (this.closed)
202
+ return;
203
+ this.language = (_a = options.language) !== null && _a !== void 0 ? _a : this.language;
204
+ this.hints = (_b = options.hints) !== null && _b !== void 0 ? _b : this.hints;
205
+ clearTimeout(this.retry);
206
+ this.retire(this.socket, DRAIN_MS);
207
+ this.connect();
208
+ }
209
+ /**
210
+ * Ends the audio and waits (up to timeoutMs) for Deepgram to send its last results and close
211
+ * the socket, then closes the stream. Deepgram works through audio at about real-time pace even
212
+ * when it arrives faster, so a fixed wait followed by close() can drop every final (PR5, VO-B1).
213
+ */
214
+ finish() {
215
+ return __awaiter(this, arguments, void 0, function* (timeoutMs = 10000) {
216
+ if (this.closed)
217
+ return;
218
+ this.finishing = true;
219
+ clearTimeout(this.retry);
220
+ clearInterval(this.keepalive);
221
+ const s = this.socket;
222
+ if (s.readyState === ws_1.default.OPEN) {
223
+ const done = new Promise((resolve) => {
224
+ const timer = setTimeout(resolve, timeoutMs);
225
+ s.once("close", () => {
226
+ clearTimeout(timer);
227
+ resolve();
228
+ });
229
+ });
230
+ s.send(JSON.stringify({ type: "CloseStream" }));
231
+ yield done;
232
+ }
233
+ this.close();
234
+ });
235
+ }
236
+ close() {
237
+ this.closed = true;
238
+ clearTimeout(this.retry);
239
+ this.retire(this.socket, 0);
240
+ this.queue = [];
241
+ this.removeAllListeners();
242
+ }
243
+ retire(s, drainMs) {
244
+ clearInterval(this.keepalive);
245
+ if (s.readyState !== ws_1.default.OPEN) {
246
+ s.terminate();
247
+ return;
248
+ }
249
+ s.send(JSON.stringify({ type: "Finalize" }));
250
+ s.send(JSON.stringify({ type: "CloseStream" }));
251
+ if (!drainMs)
252
+ return s.close();
253
+ // Deepgram closes the socket itself once it has sent the last results.
254
+ const timer = setTimeout(() => s.terminate(), drainMs);
255
+ s.once("close", () => clearTimeout(timer));
256
+ }
257
+ }
258
+ exports.DeepgramSttStream = DeepgramSttStream;
259
+ class DeepgramSttAdapter {
260
+ constructor(options) {
261
+ this.options = options;
262
+ }
263
+ open(options) {
264
+ var _a;
265
+ return new DeepgramSttStream(this.options, options.language, options.hints, (_a = options.model) !== null && _a !== void 0 ? _a : this.options.sttModel);
266
+ }
267
+ }
268
+ exports.DeepgramSttAdapter = DeepgramSttAdapter;
@@ -0,0 +1,16 @@
1
+ import { Synthesis, TtsAdapter } from "../speech";
2
+ export type ElevenLabsOptions = {
3
+ apiKey: string;
4
+ baseUrl?: string;
5
+ model?: string;
6
+ timeoutMs?: number;
7
+ };
8
+ export declare class ElevenLabsTtsAdapter implements TtsAdapter {
9
+ private readonly options;
10
+ constructor(options: ElevenLabsOptions);
11
+ synthesize(request: {
12
+ text: string;
13
+ voice: string;
14
+ language: string;
15
+ }, signal: AbortSignal): Promise<Synthesis>;
16
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.ElevenLabsTtsAdapter = void 0;
13
+ /**
14
+ * Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
15
+ * http://github.com/fonoster/fonoster
16
+ *
17
+ * This file is part of Fonoster
18
+ *
19
+ * Licensed under the MIT License (the "License");
20
+ * you may not use this file except in compliance with
21
+ * the License. You may obtain a copy of the License at
22
+ *
23
+ * https://opensource.org/licenses/MIT
24
+ *
25
+ * Unless required by applicable law or agreed to in writing, software
26
+ * distributed under the License is distributed on an "AS IS" BASIS,
27
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28
+ * See the License for the specific language governing permissions and
29
+ * limitations under the License.
30
+ */
31
+ const http_1 = require("../http");
32
+ class ElevenLabsTtsAdapter {
33
+ constructor(options) {
34
+ this.options = options;
35
+ }
36
+ synthesize(request, signal) {
37
+ return __awaiter(this, void 0, void 0, function* () {
38
+ var _a, _b, _c;
39
+ // The voice is an ElevenLabs voice id (catalog id "elevenlabs/<voice id>").
40
+ const audio = yield (0, http_1.streamingPost)(`${(_a = this.options.baseUrl) !== null && _a !== void 0 ? _a : "https://api.elevenlabs.io"}/v1/text-to-speech/${encodeURIComponent(request.voice)}/stream?output_format=pcm_16000`, {
41
+ headers: {
42
+ "xi-api-key": this.options.apiKey,
43
+ "content-type": "application/json"
44
+ },
45
+ body: JSON.stringify({
46
+ text: request.text,
47
+ model_id: (_b = this.options.model) !== null && _b !== void 0 ? _b : "eleven_flash_v2_5"
48
+ })
49
+ }, signal, (_c = this.options.timeoutMs) !== null && _c !== void 0 ? _c : 3000, "ElevenLabs");
50
+ return { audio };
51
+ });
52
+ }
53
+ }
54
+ exports.ElevenLabsTtsAdapter = ElevenLabsTtsAdapter;
@@ -0,0 +1,24 @@
1
+ import { SttAdapter, SttStream, Synthesis, TtsAdapter } from "../speech";
2
+ export declare class FakeTtsAdapter implements TtsAdapter {
3
+ private readonly options;
4
+ constructor(options: {
5
+ url: string;
6
+ timeoutMs: number;
7
+ });
8
+ synthesize(request: {
9
+ text: string;
10
+ voice: string;
11
+ language: string;
12
+ }, signal: AbortSignal): Promise<Synthesis>;
13
+ }
14
+ export declare class FakeSttAdapter implements SttAdapter {
15
+ private readonly options;
16
+ constructor(options: {
17
+ url: string;
18
+ });
19
+ open(options: {
20
+ language: string;
21
+ hints?: string[];
22
+ model?: string;
23
+ }): SttStream;
24
+ }