@rimelabs/sdk 0.1.0-alpha.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/dist/policy.js ADDED
@@ -0,0 +1,73 @@
1
+ import { RimeInputError } from "./errors.js";
2
+ export const policy = {
3
+ target: "coda.api.rime.ai:443",
4
+ audience: "coda.api.rime.ai",
5
+ exchangeUrl: "https://themis.api.rime.ai/v1/token",
6
+ sentenceBytes: 65536,
7
+ sourceChars: 1024,
8
+ outputBytes: 96000,
9
+ outputChunkBytes: 9600,
10
+ receiveBytes: 4194304,
11
+ authTimeout: 10,
12
+ connectionTimeout: 10,
13
+ firstAudioTimeout: 30,
14
+ progressTimeout: 60,
15
+ discoveryTimeout: 10,
16
+ cleanupTimeout: 2,
17
+ };
18
+ export function resolve(model, endpoint) {
19
+ // Add model defaults here only when its transport contract is supported.
20
+ if (model !== "coda")
21
+ throw new RimeInputError("This SDK supports model='coda'");
22
+ if (endpoint === undefined || endpoint === null)
23
+ return { ...policy };
24
+ const error = "endpoint must be a hostname with an optional port (1-65535), without a scheme or path";
25
+ if (typeof endpoint !== "string" || /\s/.test(endpoint))
26
+ throw new RimeInputError(error);
27
+ const [host, port, extra] = endpoint.split(":");
28
+ if (!host ||
29
+ host.length > 253 ||
30
+ extra !== undefined ||
31
+ host
32
+ .split(".")
33
+ .some((label) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test(label)) ||
34
+ (port !== undefined &&
35
+ (!/^[0-9]{1,5}$/.test(port) || Number(port) < 1 || Number(port) > 65535)))
36
+ throw new RimeInputError(error);
37
+ return {
38
+ ...policy,
39
+ target: `${host.toLowerCase()}:${port === undefined ? 443 : Number(port)}`,
40
+ audience: host.toLowerCase(),
41
+ };
42
+ }
43
+ export function timeout(value, inherited = null) {
44
+ if (value === undefined)
45
+ return inherited;
46
+ if (value !== null &&
47
+ (typeof value !== "number" || !Number.isFinite(value) || value <= 0))
48
+ throw new RimeInputError("timeout must be finite positive seconds or null");
49
+ return value;
50
+ }
51
+ export function nonempty(value, name) {
52
+ if (typeof value !== "string" || !value.trim())
53
+ throw new RimeInputError(`${name} must be a non-empty string`);
54
+ return value;
55
+ }
56
+ export function abortable(promise, signal) {
57
+ if (signal.aborted)
58
+ return Promise.reject(signal.reason);
59
+ return new Promise((resolve, reject) => {
60
+ const abort = () => {
61
+ signal.removeEventListener("abort", abort);
62
+ reject(signal.reason);
63
+ };
64
+ signal.addEventListener("abort", abort, { once: true });
65
+ Promise.resolve(promise).then((value) => {
66
+ signal.removeEventListener("abort", abort);
67
+ resolve(value);
68
+ }, (error) => {
69
+ signal.removeEventListener("abort", abort);
70
+ reject(error);
71
+ });
72
+ });
73
+ }
@@ -0,0 +1,19 @@
1
+ export declare class ByteQueue {
2
+ private readonly limit;
3
+ private byteCount;
4
+ private items;
5
+ private closed;
6
+ private error;
7
+ private waiters;
8
+ private writers;
9
+ private readonly chunkSize;
10
+ constructor(limit: number, chunkSize: number);
11
+ get size(): number;
12
+ get hasPendingOutput(): boolean;
13
+ private wait;
14
+ private wake;
15
+ put(data: Buffer): Promise<void>;
16
+ get(): Promise<IteratorResult<Uint8Array>>;
17
+ finish(): void;
18
+ fail(error: Error): void;
19
+ }
package/dist/queue.js ADDED
@@ -0,0 +1,78 @@
1
+ // finish() drains queued audio; fail() discards it and keeps the first error.
2
+ // Subsequent puts discard their input. Await the last put before finish().
3
+ // AudioStream owns consumer-observed completion and checks for cancellation
4
+ // after awaiting get(), since cancellation can follow promise resolution.
5
+ export class ByteQueue {
6
+ limit;
7
+ byteCount = 0;
8
+ items = [];
9
+ closed = false;
10
+ error = null;
11
+ waiters = new Set();
12
+ writers = 0;
13
+ chunkSize;
14
+ constructor(limit, chunkSize) {
15
+ this.limit = limit;
16
+ if (!Number.isSafeInteger(limit) ||
17
+ !Number.isSafeInteger(chunkSize) ||
18
+ limit <= 0 ||
19
+ chunkSize <= 0)
20
+ throw new RangeError("Audio queue limits must be positive integers");
21
+ this.chunkSize = Math.min(chunkSize, limit);
22
+ }
23
+ get size() {
24
+ return this.byteCount;
25
+ }
26
+ get hasPendingOutput() {
27
+ return this.byteCount > 0 || this.writers > 0;
28
+ }
29
+ wait() {
30
+ return new Promise((resolve) => this.waiters.add(resolve));
31
+ }
32
+ wake() {
33
+ for (const resolve of this.waiters)
34
+ resolve();
35
+ this.waiters.clear();
36
+ }
37
+ async put(data) {
38
+ this.writers++;
39
+ try {
40
+ for (let offset = 0; offset < data.length; offset += this.chunkSize) {
41
+ const part = data.subarray(offset, offset + this.chunkSize);
42
+ while (!this.closed && this.byteCount + part.length > this.limit)
43
+ await this.wait();
44
+ if (this.closed)
45
+ return;
46
+ this.items.push(part);
47
+ this.byteCount += part.length;
48
+ this.wake();
49
+ }
50
+ }
51
+ finally {
52
+ this.writers--;
53
+ }
54
+ }
55
+ async get() {
56
+ while (!this.closed && !this.items.length)
57
+ await this.wait();
58
+ if (this.error)
59
+ throw this.error;
60
+ const value = this.items.shift();
61
+ if (value) {
62
+ this.byteCount -= value.length;
63
+ this.wake();
64
+ return { done: false, value };
65
+ }
66
+ return { done: true, value: undefined };
67
+ }
68
+ finish() {
69
+ this.closed = true;
70
+ this.wake();
71
+ }
72
+ fail(error) {
73
+ this.error ??= error;
74
+ this.items = [];
75
+ this.byteCount = 0;
76
+ this.finish();
77
+ }
78
+ }
@@ -0,0 +1,21 @@
1
+ interface BlingFire {
2
+ ready: Promise<BlingFire>;
3
+ _malloc(n: number): number;
4
+ _free(p: number): void;
5
+ _TextToSentences(input: number, length: number, output: number, capacity: number): number;
6
+ HEAPU8: Uint8Array;
7
+ }
8
+ export declare const ready: Promise<BlingFire>;
9
+ export declare class SentenceBuffer {
10
+ private limit;
11
+ private pending;
12
+ private committed;
13
+ private parts;
14
+ private untilScan;
15
+ private sincePunctuation;
16
+ private highSurrogate;
17
+ constructor(limit: number);
18
+ feed(fragment: string, final?: boolean): Generator<string>;
19
+ private scan;
20
+ }
21
+ export {};
@@ -0,0 +1,142 @@
1
+ import { createRequire } from "node:module";
2
+ import { RimeInputError, RimeResourceLimitError } from "./errors.js";
3
+ const require = createRequire(import.meta.url);
4
+ const module = require("../vendor/blingfire.cjs");
5
+ export const ready = module.ready;
6
+ // These marks schedule scans. Only BlingFire decides sentence boundaries.
7
+ // Keep the set and code-point counts in sync with Python's _sentences.py.
8
+ const scanTriggers = new Set("\r\n.!?\u01c3\u06d4\u061f\u2024\u2026\u203c\u203d\u2048\u2049" +
9
+ "\u2404\ufe52\uff0e\uff61\u3002\uff1f\uff01\u2028\u2029\u00bf\u00a1");
10
+ const lookahead = 16, scanInterval = 1024, contextSize = 128;
11
+ function sentenceEnds(text) {
12
+ if (!text.trim())
13
+ return [];
14
+ const input = Buffer.from(text), capacity = input.length * 3 + 16;
15
+ const source = module._malloc(input.length + 1), output = module._malloc(capacity);
16
+ let normalized;
17
+ try {
18
+ module.HEAPU8.set(input, source);
19
+ module.HEAPU8[source + input.length] = 0;
20
+ const length = module._TextToSentences(source, input.length, output, capacity);
21
+ if (length < 0 || length > capacity)
22
+ throw new RimeInputError("Sentence detection failed");
23
+ normalized = Buffer.from(module.HEAPU8.subarray(output, output + length))
24
+ .toString("utf8")
25
+ .replace(/\0+$/, "");
26
+ }
27
+ finally {
28
+ module._free(source);
29
+ module._free(output);
30
+ }
31
+ let position = 0;
32
+ const ends = [];
33
+ for (const sentence of normalized.split("\n")) {
34
+ if (!sentence.trim())
35
+ continue;
36
+ for (const char of sentence) {
37
+ if (/\s/u.test(char))
38
+ continue;
39
+ // BlingFire removes zero-width spaces, direction marks, and BOM at
40
+ // boundaries. Match first to preserve marks retained inside a sentence.
41
+ while (position < text.length &&
42
+ text[position] !== char &&
43
+ /[\s\u200b\u200e\u200f\ufeff]/u.test(text[position]))
44
+ position++;
45
+ const original = String.fromCodePoint(text.codePointAt(position) ?? 0);
46
+ if (original !== char)
47
+ throw new RimeInputError("Sentence detector could not preserve source offsets");
48
+ position += char.length;
49
+ }
50
+ ends.push(position);
51
+ }
52
+ return ends;
53
+ }
54
+ export class SentenceBuffer {
55
+ limit;
56
+ pending = "";
57
+ committed = 0;
58
+ parts = [];
59
+ untilScan = scanInterval;
60
+ sincePunctuation = lookahead;
61
+ highSurrogate = "";
62
+ constructor(limit) {
63
+ this.limit = limit;
64
+ }
65
+ *feed(fragment, final = false) {
66
+ fragment = this.highSurrogate + fragment;
67
+ this.highSurrogate = "";
68
+ if (!final && /[\uD800-\uDBFF]$/.test(fragment)) {
69
+ this.highSurrogate = fragment.slice(-1);
70
+ fragment = fragment.slice(0, -1);
71
+ }
72
+ for (const char of fragment) {
73
+ this.parts.push(char);
74
+ this.untilScan--;
75
+ this.sincePunctuation = Math.min(this.sincePunctuation + 1, lookahead);
76
+ if (scanTriggers.has(char)) {
77
+ this.sincePunctuation = 0;
78
+ this.untilScan = Math.min(this.untilScan, lookahead);
79
+ }
80
+ if (this.untilScan === 0)
81
+ yield* this.scan(false);
82
+ }
83
+ if (final)
84
+ yield* this.scan(true);
85
+ }
86
+ *scan(final) {
87
+ this.pending += this.parts.join("");
88
+ this.parts = [];
89
+ if (!this.pending)
90
+ return;
91
+ const ends = sentenceEnds(this.pending);
92
+ if (final && ends.length)
93
+ ends[ends.length - 1] = this.pending.length;
94
+ // Retained lookahead can contain multiple separately limited sentences.
95
+ let start = this.committed;
96
+ for (const end of [...ends, this.pending.length]) {
97
+ if (end <= start)
98
+ continue;
99
+ if (Buffer.byteLength(this.pending.slice(start, end)) > this.limit)
100
+ throw new RimeResourceLimitError("Sentence exceeds the supported byte limit");
101
+ start = end;
102
+ }
103
+ const committedEnds = final
104
+ ? ends
105
+ : ends
106
+ .slice(0, -1)
107
+ .filter((end) => [...this.pending.slice(end).trim()].length >= lookahead);
108
+ for (const end of committedEnds) {
109
+ if (end <= this.committed)
110
+ continue;
111
+ const sentence = this.pending.slice(this.committed, end);
112
+ this.committed = end;
113
+ if (sentence.trim())
114
+ yield sentence;
115
+ }
116
+ if (final) {
117
+ const residual = this.pending.slice(this.committed);
118
+ if (residual.trim())
119
+ yield residual;
120
+ this.pending = "";
121
+ this.committed = 0;
122
+ this.untilScan = scanInterval;
123
+ this.sincePunctuation = lookahead;
124
+ return;
125
+ }
126
+ this.untilScan =
127
+ this.sincePunctuation < lookahead ? lookahead : scanInterval;
128
+ for (const end of ends) {
129
+ if (this.committed < end && end < this.pending.length) {
130
+ const needed = Math.max(1, lookahead - [...this.pending.slice(end).trimStart()].length);
131
+ this.untilScan = Math.min(this.untilScan, needed);
132
+ break;
133
+ }
134
+ }
135
+ // Bound history by code points, without cutting a UTF-16 surrogate pair.
136
+ const context = [...this.pending.slice(0, this.committed)]
137
+ .slice(-contextSize)
138
+ .join("");
139
+ this.pending = context + this.pending.slice(this.committed);
140
+ this.committed = context.length;
141
+ }
142
+ }
@@ -0,0 +1,48 @@
1
+ import { AudioFormat } from "./audio.js";
2
+ import { type PreparedConnection } from "./transport.js";
3
+ export interface StreamOwner {
4
+ prepare(signal: AbortSignal): Promise<PreparedConnection>;
5
+ checkOpen(): void;
6
+ forget(stream: AudioStream): void;
7
+ }
8
+ export type TextSource = string | AsyncIterable<string>;
9
+ export declare const constructionKey: unique symbol;
10
+ export declare class AudioStream implements AsyncIterableIterator<Uint8Array> {
11
+ private owner;
12
+ private source;
13
+ private voice;
14
+ private language;
15
+ private formatValue;
16
+ private timeout;
17
+ private readonly controller;
18
+ private readonly queue;
19
+ private worker;
20
+ private timer;
21
+ private call;
22
+ private failure;
23
+ private finished;
24
+ private reading;
25
+ private inputDone;
26
+ private sourceWaiting;
27
+ private submitted;
28
+ private received;
29
+ private startedAt;
30
+ private progressAt;
31
+ private readonly operationId;
32
+ private bytesReceived;
33
+ private bytesDelivered;
34
+ constructor(key: symbol, owner: StreamOwner, source: TextSource | null, voice: string, language: string, formatValue: AudioFormat, timeout: number | null);
35
+ get format(): AudioFormat;
36
+ get requestId(): string | null;
37
+ private start;
38
+ private fail;
39
+ private produce;
40
+ private read;
41
+ private run;
42
+ [Symbol.asyncIterator](): this;
43
+ next(): Promise<IteratorResult<Uint8Array>>;
44
+ private cleanup;
45
+ cancel(): Promise<void>;
46
+ return(): Promise<IteratorResult<Uint8Array>>;
47
+ [Symbol.asyncDispose](): Promise<void>;
48
+ }
package/dist/stream.js ADDED
@@ -0,0 +1,273 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { debuglog } from "node:util";
3
+ const log = debuglog("rime-sdk");
4
+ import { Converter } from "./audio.js";
5
+ import { policy, abortable } from "./policy.js";
6
+ import { ByteQueue } from "./queue.js";
7
+ import { SentenceBuffer, ready } from "./sentences.js";
8
+ import { SynthesisCall } from "./transport.js";
9
+ import { RimeError, RimeInputError, RimeCancelledError, RimeTimeoutError, RimeStreamError, } from "./errors.js";
10
+ export const constructionKey = Symbol("private stream constructor");
11
+ export class AudioStream {
12
+ owner;
13
+ source;
14
+ voice;
15
+ language;
16
+ formatValue;
17
+ timeout;
18
+ controller = new AbortController();
19
+ queue = new ByteQueue(policy.outputBytes, policy.outputChunkBytes);
20
+ worker = null;
21
+ timer = null;
22
+ call = null;
23
+ failure = null;
24
+ finished = false;
25
+ reading = false;
26
+ inputDone = false;
27
+ sourceWaiting = false;
28
+ submitted = false;
29
+ received = false;
30
+ startedAt = 0;
31
+ progressAt = 0;
32
+ operationId = randomUUID();
33
+ bytesReceived = 0;
34
+ bytesDelivered = 0;
35
+ constructor(key, owner, source, voice, language, formatValue, timeout) {
36
+ this.owner = owner;
37
+ this.source = source;
38
+ this.voice = voice;
39
+ this.language = language;
40
+ this.formatValue = formatValue;
41
+ this.timeout = timeout;
42
+ if (key !== constructionKey)
43
+ throw new TypeError("AudioStream is returned by client.tts.stream()");
44
+ }
45
+ get format() {
46
+ return this.formatValue;
47
+ }
48
+ get requestId() {
49
+ return this.call?.requestId ?? null;
50
+ }
51
+ start() {
52
+ if (this.failure)
53
+ throw this.failure;
54
+ if (this.finished || this.worker)
55
+ return;
56
+ this.owner.checkOpen();
57
+ this.startedAt = performance.now();
58
+ this.worker = this.run();
59
+ this.timer = setInterval(() => {
60
+ if (this.finished || this.failure)
61
+ return;
62
+ const now = performance.now();
63
+ if (this.timeout !== null &&
64
+ now - this.startedAt >= this.timeout * 1000) {
65
+ this.fail(new RimeTimeoutError("Overall synthesis deadline expired", this.requestId));
66
+ return;
67
+ }
68
+ if (this.call &&
69
+ !this.call.done &&
70
+ this.submitted &&
71
+ !this.sourceWaiting &&
72
+ !this.queue.hasPendingOutput) {
73
+ const limit = this.received
74
+ ? policy.progressTimeout
75
+ : policy.firstAudioTimeout;
76
+ if (now - this.progressAt >= limit * 1000)
77
+ this.fail(new RimeTimeoutError("Synthesis output stopped making progress", this.requestId));
78
+ }
79
+ else
80
+ this.progressAt = now;
81
+ }, 20);
82
+ }
83
+ fail(error) {
84
+ if (this.finished || this.failure)
85
+ return;
86
+ if (error.requestId === null && this.requestId !== null)
87
+ error = new error.constructor(error.message, this.requestId, { cause: error.cause });
88
+ this.failure = error;
89
+ if (this.timer)
90
+ clearInterval(this.timer);
91
+ this.queue.fail(error);
92
+ this.controller.abort(error);
93
+ this.call?.cancel();
94
+ }
95
+ async produce() {
96
+ const source = this.source;
97
+ let iterator;
98
+ const buffer = new SentenceBuffer(policy.sentenceBytes);
99
+ let meaningful = false;
100
+ try {
101
+ try {
102
+ iterator = (typeof source === "string"
103
+ ? (async function* () {
104
+ yield source;
105
+ })()
106
+ : source)[Symbol.asyncIterator]();
107
+ }
108
+ catch (error) {
109
+ if (this.controller.signal.aborted)
110
+ throw this.controller.signal.reason;
111
+ throw new RimeInputError("The text source failed", this.requestId, {
112
+ cause: error,
113
+ });
114
+ }
115
+ for (;;) {
116
+ this.sourceWaiting = true;
117
+ let result;
118
+ try {
119
+ result = await abortable(iterator.next(), this.controller.signal);
120
+ }
121
+ catch (error) {
122
+ if (this.controller.signal.aborted)
123
+ throw this.controller.signal.reason;
124
+ throw new RimeInputError("The text source failed", this.requestId, {
125
+ cause: error,
126
+ });
127
+ }
128
+ finally {
129
+ this.sourceWaiting = false;
130
+ }
131
+ if (result.done)
132
+ break;
133
+ const chunk = result.value;
134
+ if (typeof chunk !== "string")
135
+ throw new RimeInputError("The text source must yield strings");
136
+ if (chunk.trim())
137
+ meaningful = true;
138
+ // Never split a surrogate pair when limiting native detector input size.
139
+ for (let offset = 0; offset < chunk.length;) {
140
+ let end = Math.min(chunk.length, offset + policy.sourceChars);
141
+ if (end < chunk.length && /[\uD800-\uDBFF]/.test(chunk[end - 1]))
142
+ end++;
143
+ for (const sentence of buffer.feed(chunk.slice(offset, end))) {
144
+ if (!this.submitted)
145
+ this.progressAt = performance.now();
146
+ this.submitted = true;
147
+ await this.call.write(sentence);
148
+ }
149
+ offset = end;
150
+ }
151
+ }
152
+ if (!meaningful)
153
+ throw new RimeInputError("The text source contained no meaningful text");
154
+ for (const sentence of buffer.feed("", true)) {
155
+ if (!this.submitted)
156
+ this.progressAt = performance.now();
157
+ this.submitted = true;
158
+ await this.call.write(sentence);
159
+ }
160
+ this.call.finishInput();
161
+ this.inputDone = true;
162
+ }
163
+ catch (error) {
164
+ if (error instanceof RimeError || this.controller.signal.aborted)
165
+ throw error;
166
+ throw new RimeInputError("The text source failed", this.requestId, {
167
+ cause: error,
168
+ });
169
+ }
170
+ finally {
171
+ if (iterator?.return) {
172
+ const cleanup = Promise.resolve(iterator.return());
173
+ cleanup.catch(() => { });
174
+ let timer;
175
+ await Promise.race([
176
+ cleanup,
177
+ new Promise((resolve) => {
178
+ timer = setTimeout(resolve, policy.cleanupTimeout * 1000);
179
+ }),
180
+ ]).catch(() => { });
181
+ clearTimeout(timer);
182
+ }
183
+ }
184
+ }
185
+ async read() {
186
+ const converter = new Converter(this.format);
187
+ for await (const data of this.call.audio()) {
188
+ this.progressAt = performance.now();
189
+ if (data.length) {
190
+ this.received = true;
191
+ this.bytesReceived += data.length;
192
+ await this.queue.put(converter.process(data));
193
+ }
194
+ }
195
+ if (!this.inputDone)
196
+ throw new RimeStreamError("The service completed before input finished", this.requestId);
197
+ await this.queue.put(converter.process(new Uint8Array(), true));
198
+ }
199
+ async run() {
200
+ let tasks = [];
201
+ try {
202
+ await abortable(ready, this.controller.signal);
203
+ const prepared = await this.owner.prepare(this.controller.signal);
204
+ this.call = new SynthesisCall(prepared, this.controller.signal);
205
+ await this.call.start(this.voice, this.language);
206
+ tasks = [this.produce(), this.read()];
207
+ await Promise.all(tasks);
208
+ this.queue.finish();
209
+ }
210
+ catch (error) {
211
+ if (error instanceof RimeError)
212
+ this.fail(error);
213
+ else
214
+ this.fail(new RimeStreamError("Synthesis failed", this.requestId, {
215
+ cause: error,
216
+ }));
217
+ }
218
+ finally {
219
+ if (this.failure)
220
+ this.controller.abort(this.failure);
221
+ await Promise.allSettled(tasks);
222
+ this.source = null;
223
+ if (this.failure)
224
+ this.owner.forget(this);
225
+ log("operation=%s request=%s received=%d delivered=%d duration_ms=%d", this.operationId, this.requestId, this.bytesReceived, this.bytesDelivered, Math.round(performance.now() - this.startedAt));
226
+ }
227
+ }
228
+ [Symbol.asyncIterator]() {
229
+ return this;
230
+ }
231
+ async next() {
232
+ if (this.reading)
233
+ throw new RimeInputError("AudioStream permits only one concurrent reader");
234
+ this.reading = true;
235
+ try {
236
+ this.start();
237
+ const result = await this.queue.get();
238
+ if (this.failure)
239
+ throw this.failure;
240
+ if (result.done) {
241
+ await this.cleanup();
242
+ if (this.failure)
243
+ throw this.failure;
244
+ this.finished = true;
245
+ }
246
+ if (!result.done)
247
+ this.bytesDelivered += result.value.byteLength;
248
+ return result;
249
+ }
250
+ finally {
251
+ this.reading = false;
252
+ }
253
+ }
254
+ async cleanup() {
255
+ if (this.timer)
256
+ clearInterval(this.timer);
257
+ await this.worker;
258
+ this.owner.forget(this);
259
+ }
260
+ async cancel() {
261
+ if (!this.finished && !this.failure)
262
+ this.fail(new RimeCancelledError("Synthesis cancelled", this.requestId));
263
+ await this.cleanup();
264
+ this.source = null;
265
+ }
266
+ async return() {
267
+ await this.cancel();
268
+ return { done: true, value: undefined };
269
+ }
270
+ async [Symbol.asyncDispose]() {
271
+ await this.cancel();
272
+ }
273
+ }
@@ -0,0 +1,34 @@
1
+ import * as grpc from "@grpc/grpc-js";
2
+ import * as errors from "./errors.js";
3
+ export type Connection = grpc.Client;
4
+ export interface PreparedConnection {
5
+ client: Connection;
6
+ metadata: grpc.Metadata;
7
+ }
8
+ export type DiscoveryKind = "voices" | "languages";
9
+ export declare function rpcError(code: number, requestId?: string | null): errors.RimeError;
10
+ export declare const transport: {
11
+ makeClient: (target: string) => grpc.Client;
12
+ };
13
+ export declare function connect(client: grpc.Client, signal: AbortSignal): Promise<void>;
14
+ export declare function metadata(value: string): grpc.Metadata;
15
+ export declare function discover(prepared: PreparedConnection, kind: DiscoveryKind, language: string | null | undefined, signal: AbortSignal): Promise<string[]>;
16
+ export declare class SynthesisCall {
17
+ private signal;
18
+ private readonly call;
19
+ private readonly metadataPromise;
20
+ private readonly statusPromise;
21
+ private status;
22
+ private id;
23
+ constructor(prepared: PreparedConnection, signal: AbortSignal);
24
+ get requestId(): string | null;
25
+ get done(): boolean;
26
+ cancel(): void;
27
+ private error;
28
+ private writeMessage;
29
+ start(voice: string, language: string): Promise<void>;
30
+ write(sentence: string): Promise<void>;
31
+ finishInput(): void;
32
+ private checkFormat;
33
+ audio(): AsyncGenerator<Uint8Array>;
34
+ }