@rimelabs/sdk 0.1.0-alpha.1 → 0.1.0-alpha.12
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/CHANGELOG.md +96 -11
- package/LICENSE-PRISM +201 -0
- package/NOTICE-PRISM +9 -0
- package/README.md +175 -63
- package/dist/auth.d.ts +9 -3
- package/dist/auth.js +12 -4
- package/dist/cancellation.d.ts +6 -0
- package/dist/cancellation.js +54 -0
- package/dist/client.d.ts +23 -23
- package/dist/client.js +39 -92
- package/dist/grpc.d.ts +27 -0
- package/dist/{transport.js → grpc.js} +36 -106
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -2
- package/dist/pcm.d.ts +21 -0
- package/dist/pcm.js +52 -0
- package/dist/realtime/async.d.ts +23 -0
- package/dist/realtime/async.js +87 -0
- package/dist/realtime/audio.d.ts +2 -0
- package/dist/realtime/audio.js +11 -0
- package/dist/realtime/client.d.ts +12 -0
- package/dist/realtime/client.js +156 -0
- package/dist/realtime/protocol.d.ts +80 -0
- package/dist/realtime/protocol.js +354 -0
- package/dist/realtime/session.d.ts +67 -0
- package/dist/realtime/session.js +731 -0
- package/dist/realtime/transport.d.ts +16 -0
- package/dist/realtime/transport.js +83 -0
- package/dist/realtime/types.d.ts +186 -0
- package/dist/realtime/types.js +10 -0
- package/dist/stt/audio.d.ts +15 -0
- package/dist/stt/audio.js +46 -0
- package/dist/stt/client.d.ts +13 -0
- package/dist/stt/client.js +73 -0
- package/dist/stt/policy.d.ts +11 -0
- package/dist/stt/policy.js +11 -0
- package/dist/stt/protocol.d.ts +13 -0
- package/dist/stt/protocol.js +61 -0
- package/dist/stt/queue.d.ts +16 -0
- package/dist/stt/queue.js +45 -0
- package/dist/stt/stream.d.ts +48 -0
- package/dist/stt/stream.js +224 -0
- package/dist/stt/transport.d.ts +13 -0
- package/dist/stt/transport.js +45 -0
- package/dist/stt/types.d.ts +34 -0
- package/dist/stt/types.js +1 -0
- package/dist/{audio.js → tts/audio.js} +3 -2
- package/dist/tts/client.d.ts +39 -0
- package/dist/tts/client.js +121 -0
- package/dist/{policy.d.ts → tts/policy.d.ts} +2 -2
- package/dist/tts/policy.js +41 -0
- package/dist/{sentences.js → tts/sentences.js} +2 -2
- package/dist/{stream.d.ts → tts/stream.d.ts} +1 -1
- package/dist/{stream.js → tts/stream.js} +5 -8
- package/dist/{transport.d.ts → tts/transport.d.ts} +3 -11
- package/dist/tts/transport.js +116 -0
- package/dist/validation.d.ts +5 -0
- package/dist/validation.js +28 -0
- package/docs/realtime.md +220 -0
- package/docs/stt.md +103 -0
- package/docs/tts.md +139 -0
- package/package.json +14 -6
- package/dist/policy.js +0 -73
- /package/dist/{audio.d.ts → tts/audio.d.ts} +0 -0
- /package/dist/{queue.d.ts → tts/queue.d.ts} +0 -0
- /package/dist/{queue.js → tts/queue.js} +0 -0
- /package/dist/{sentences.d.ts → tts/sentences.d.ts} +0 -0
package/dist/auth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { abortable } from "./cancellation.js";
|
|
2
2
|
import { RimeError, RimeAuthenticationError, RimePermissionError, RimeTimeoutError, RimeCancelledError, RimeResourceLimitError, RimeUnavailableError, } from "./errors.js";
|
|
3
|
-
export async function exchangeKey(key, signal, configuration
|
|
3
|
+
export async function exchangeKey(key, signal, configuration) {
|
|
4
4
|
try {
|
|
5
5
|
const response = await fetch(configuration.exchangeUrl, {
|
|
6
6
|
method: "POST",
|
|
@@ -77,17 +77,25 @@ export class Credentials {
|
|
|
77
77
|
refreshAt = 0;
|
|
78
78
|
controller = new AbortController();
|
|
79
79
|
closed = false;
|
|
80
|
-
constructor(key, configuration
|
|
80
|
+
constructor(key, configuration) {
|
|
81
81
|
this.key = key;
|
|
82
82
|
this.configuration = configuration;
|
|
83
83
|
}
|
|
84
|
+
authorization() {
|
|
85
|
+
if (this.closed)
|
|
86
|
+
throw new RimeAuthenticationError("Credentials are closed");
|
|
87
|
+
return `Bearer ${this.key}`;
|
|
88
|
+
}
|
|
89
|
+
realtimeAuthorization() {
|
|
90
|
+
return this.authorization();
|
|
91
|
+
}
|
|
84
92
|
async metadata(signal) {
|
|
85
93
|
// TEMPORARY until Themis is ready: replace this body with the call below.
|
|
86
94
|
// return this.themisMetadata(signal);
|
|
87
95
|
if (this.closed)
|
|
88
96
|
throw new RimeAuthenticationError("Credentials are closed");
|
|
89
97
|
signal.throwIfAborted();
|
|
90
|
-
return
|
|
98
|
+
return this.authorization();
|
|
91
99
|
}
|
|
92
100
|
async themisMetadata(signal) {
|
|
93
101
|
if (this.closed)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function abortable<T>(promise: PromiseLike<T>, signal: AbortSignal): Promise<T>;
|
|
2
|
+
/** A disposable deadline avoids retaining timers and caller abort listeners. */
|
|
3
|
+
export declare function scope(signals: (AbortSignal | undefined)[], seconds?: number, error?: Error): {
|
|
4
|
+
signal: AbortSignal;
|
|
5
|
+
dispose(): void;
|
|
6
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export function abortable(promise, signal) {
|
|
2
|
+
if (signal.aborted)
|
|
3
|
+
return Promise.reject(signal.reason);
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const abort = () => {
|
|
6
|
+
signal.removeEventListener("abort", abort);
|
|
7
|
+
reject(signal.reason);
|
|
8
|
+
};
|
|
9
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
10
|
+
Promise.resolve(promise).then((value) => {
|
|
11
|
+
signal.removeEventListener("abort", abort);
|
|
12
|
+
resolve(value);
|
|
13
|
+
}, (error) => {
|
|
14
|
+
signal.removeEventListener("abort", abort);
|
|
15
|
+
reject(error);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** A disposable deadline avoids retaining timers and caller abort listeners. */
|
|
20
|
+
export function scope(signals, seconds, error) {
|
|
21
|
+
const controller = new AbortController();
|
|
22
|
+
const handlers = new Map();
|
|
23
|
+
for (const signal of new Set(signals)) {
|
|
24
|
+
if (!signal)
|
|
25
|
+
continue;
|
|
26
|
+
const abort = () => controller.abort(signal.reason);
|
|
27
|
+
if (signal.aborted)
|
|
28
|
+
abort();
|
|
29
|
+
else {
|
|
30
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
31
|
+
handlers.set(signal, abort);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let timer;
|
|
35
|
+
if (seconds !== undefined) {
|
|
36
|
+
const end = performance.now() + seconds * 1000;
|
|
37
|
+
const expire = () => {
|
|
38
|
+
const remaining = end - performance.now();
|
|
39
|
+
if (remaining <= 0)
|
|
40
|
+
controller.abort(error);
|
|
41
|
+
else
|
|
42
|
+
timer = setTimeout(expire, Math.min(remaining, 2147483647));
|
|
43
|
+
};
|
|
44
|
+
expire();
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
signal: controller.signal,
|
|
48
|
+
dispose() {
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
for (const [signal, handler] of handlers)
|
|
51
|
+
signal.removeEventListener("abort", handler);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { AudioSource, TranscriptionOptions } from "./stt/types.js";
|
|
2
|
+
import type { TranscriptStream } from "./stt/stream.js";
|
|
3
|
+
import { type SynthesisOptions, type DiscoveryOptions, type VoiceListOptions } from "./tts/client.js";
|
|
4
|
+
import { type AudioStream, type TextSource } from "./tts/stream.js";
|
|
5
|
+
import type { RealtimeConnectOptions } from "./realtime/types.js";
|
|
6
|
+
import type { RealtimeSession } from "./realtime/session.js";
|
|
7
|
+
export type { SynthesisOptions, DiscoveryOptions, VoiceListOptions, } from "./tts/client.js";
|
|
3
8
|
export interface RimeOptions {
|
|
4
9
|
apiKey?: string | null;
|
|
10
|
+
/** Select "coda" (default) or "mistv3" for speech and discovery. */
|
|
5
11
|
model?: string;
|
|
6
12
|
endpoint?: string | null;
|
|
7
13
|
timeout?: number | null;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
voice?: string | null;
|
|
11
|
-
language?: string;
|
|
12
|
-
audioFormat?: AudioFormat | null;
|
|
13
|
-
timeout?: number | null;
|
|
14
|
-
}
|
|
15
|
-
export interface DiscoveryOptions {
|
|
16
|
-
timeout?: number | null;
|
|
17
|
-
}
|
|
18
|
-
export interface VoiceListOptions extends DiscoveryOptions {
|
|
19
|
-
language?: string | null;
|
|
14
|
+
/** Recognition hostname and optional port; defaults to stt.api.rime.ai:443. */
|
|
15
|
+
sttEndpoint?: string | null;
|
|
20
16
|
}
|
|
21
17
|
export declare class Rime {
|
|
22
|
-
private readonly
|
|
23
|
-
private
|
|
24
|
-
private
|
|
18
|
+
private readonly credentials;
|
|
19
|
+
private readonly ttsClient;
|
|
20
|
+
private readonly sttClient;
|
|
21
|
+
readonly stt: {
|
|
22
|
+
stream: (audio: AudioSource, options: TranscriptionOptions) => TranscriptStream;
|
|
23
|
+
};
|
|
24
|
+
private readonly realtimeClient;
|
|
25
|
+
readonly realtime: {
|
|
26
|
+
connect: (options: RealtimeConnectOptions) => Promise<RealtimeSession>;
|
|
27
|
+
};
|
|
25
28
|
private closed;
|
|
26
|
-
private streams;
|
|
27
|
-
private discoveryControllers;
|
|
28
29
|
private closing;
|
|
29
|
-
private defaultTimeout;
|
|
30
30
|
readonly tts: {
|
|
31
|
+
/** Synthesize complete text and return streaming audio. Do not await this call. */
|
|
32
|
+
synthesize: (text: string, options?: SynthesisOptions) => AudioStream;
|
|
33
|
+
/** Supply incremental text and return streaming audio. Do not await this call. */
|
|
31
34
|
stream: (text: TextSource, options?: SynthesisOptions) => AudioStream;
|
|
32
35
|
};
|
|
33
36
|
readonly voices: {
|
|
@@ -38,9 +41,6 @@ export declare class Rime {
|
|
|
38
41
|
};
|
|
39
42
|
constructor(options?: RimeOptions);
|
|
40
43
|
private checkOpen;
|
|
41
|
-
private prepare;
|
|
42
|
-
private stream;
|
|
43
|
-
private discover;
|
|
44
44
|
close(): Promise<void>;
|
|
45
45
|
[Symbol.asyncDispose](): Promise<void>;
|
|
46
46
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AudioStream, constructionKey } from "./stream.js";
|
|
1
|
+
import { STT } from "./stt/client.js";
|
|
3
2
|
import { Credentials } from "./auth.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { RimeAuthenticationError, RimeInputError } from "./errors.js";
|
|
4
|
+
import { TTS, } from "./tts/client.js";
|
|
5
|
+
import { resolve, timeout } from "./tts/policy.js";
|
|
6
|
+
import { Realtime } from "./realtime/client.js";
|
|
7
7
|
export class Rime {
|
|
8
|
-
deployment;
|
|
9
8
|
credentials;
|
|
10
|
-
|
|
9
|
+
ttsClient;
|
|
10
|
+
sttClient;
|
|
11
|
+
stt;
|
|
12
|
+
realtimeClient;
|
|
13
|
+
realtime;
|
|
11
14
|
closed = false;
|
|
12
|
-
streams = new Set();
|
|
13
|
-
discoveryControllers = new Set();
|
|
14
15
|
closing = null;
|
|
15
|
-
defaultTimeout;
|
|
16
16
|
tts;
|
|
17
17
|
voices;
|
|
18
18
|
languages;
|
|
@@ -22,99 +22,46 @@ export class Rime {
|
|
|
22
22
|
: options.apiKey;
|
|
23
23
|
if (typeof key !== "string" || !key.trim())
|
|
24
24
|
throw new RimeAuthenticationError("Provide apiKey or set RIME_API_KEY");
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
this.credentials = new Credentials(key,
|
|
28
|
-
this.
|
|
29
|
-
this.
|
|
30
|
-
|
|
31
|
-
if (options.language !== undefined && options.language !== null)
|
|
32
|
-
nonempty(options.language, "language");
|
|
33
|
-
return this.discover("voices", options.language, options.timeout);
|
|
34
|
-
},
|
|
25
|
+
const defaultTimeout = timeout(options.timeout);
|
|
26
|
+
const deployment = resolve(options.model ?? "coda", options.endpoint);
|
|
27
|
+
this.credentials = new Credentials(key, deployment);
|
|
28
|
+
this.sttClient = new STT(this.credentials, options.sttEndpoint, () => this.checkOpen());
|
|
29
|
+
this.stt = {
|
|
30
|
+
stream: (audio, options) => this.sttClient.stream(audio, options),
|
|
35
31
|
};
|
|
36
|
-
this.
|
|
37
|
-
|
|
32
|
+
this.realtimeClient = new Realtime(this.credentials, () => this.checkOpen());
|
|
33
|
+
this.realtime = {
|
|
34
|
+
connect: (options) => this.realtimeClient.connect(options),
|
|
38
35
|
};
|
|
36
|
+
this.ttsClient = new TTS(this.credentials, deployment, defaultTimeout, () => this.checkOpen());
|
|
37
|
+
this.tts = {
|
|
38
|
+
synthesize: (text, options) => this.ttsClient.synthesize(text, options),
|
|
39
|
+
stream: (text, options) => this.ttsClient.stream(text, options),
|
|
40
|
+
};
|
|
41
|
+
this.voices = this.ttsClient.voices;
|
|
42
|
+
this.languages = this.ttsClient.languages;
|
|
39
43
|
}
|
|
40
44
|
checkOpen() {
|
|
41
45
|
if (this.closed)
|
|
42
46
|
throw new RimeInputError("The Rime client is closed");
|
|
43
47
|
}
|
|
44
|
-
async prepare(signal) {
|
|
45
|
-
this.checkOpen();
|
|
46
|
-
const auth = await this.credentials.metadata(signal);
|
|
47
|
-
this.checkOpen();
|
|
48
|
-
this.client ??= transport.makeClient(this.deployment.target);
|
|
49
|
-
await connect(this.client, signal);
|
|
50
|
-
return { client: this.client, metadata: metadata(auth) };
|
|
51
|
-
}
|
|
52
|
-
stream(text, options) {
|
|
53
|
-
this.checkOpen();
|
|
54
|
-
if (typeof text === "string") {
|
|
55
|
-
if (!text.trim())
|
|
56
|
-
throw new RimeInputError("Text must contain non-whitespace characters");
|
|
57
|
-
}
|
|
58
|
-
else if (!text || typeof text[Symbol.asyncIterator] !== "function")
|
|
59
|
-
throw new RimeInputError("text must be a string or an async iterable of strings");
|
|
60
|
-
const voice = options.voice === undefined || options.voice === null
|
|
61
|
-
? "clementine"
|
|
62
|
-
: nonempty(options.voice, "voice");
|
|
63
|
-
const language = nonempty(options.language ?? "en", "language");
|
|
64
|
-
const format = options.audioFormat ?? AudioFormat.PCM_24000;
|
|
65
|
-
if (format !== AudioFormat.PCM_24000 && format !== AudioFormat.MULAW_8000)
|
|
66
|
-
throw new RimeAudioFormatError("Select a named AudioFormat profile");
|
|
67
|
-
const audio = new AudioStream(constructionKey, {
|
|
68
|
-
prepare: (s) => this.prepare(s),
|
|
69
|
-
checkOpen: () => this.checkOpen(),
|
|
70
|
-
forget: (s) => this.streams.delete(s),
|
|
71
|
-
}, text, voice, language, format, timeout(options.timeout, this.defaultTimeout));
|
|
72
|
-
this.streams.add(audio);
|
|
73
|
-
return audio;
|
|
74
|
-
}
|
|
75
|
-
async discover(kind, language, override) {
|
|
76
|
-
this.checkOpen();
|
|
77
|
-
const budget = Math.min(timeout(override, this.defaultTimeout) ?? Infinity, policy.discoveryTimeout);
|
|
78
|
-
const controller = new AbortController();
|
|
79
|
-
let requestId = null;
|
|
80
|
-
this.discoveryControllers.add(controller);
|
|
81
|
-
const timer = setTimeout(() => controller.abort(new RimeTimeoutError("Discovery deadline expired")), budget * 1000);
|
|
82
|
-
try {
|
|
83
|
-
const prepared = await this.prepare(controller.signal);
|
|
84
|
-
for (let attempt = 0;; attempt++) {
|
|
85
|
-
requestId = null;
|
|
86
|
-
try {
|
|
87
|
-
return await discover(prepared, kind, language, controller.signal);
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
if (!(error instanceof RimeUnavailableError) || attempt === 2)
|
|
91
|
-
throw error;
|
|
92
|
-
requestId = error.requestId;
|
|
93
|
-
await abortable(new Promise((resolve) => setTimeout(resolve, 50 * 2 ** attempt)), controller.signal);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch (error) {
|
|
98
|
-
if (error instanceof RimeTimeoutError && requestId !== null)
|
|
99
|
-
throw new RimeTimeoutError(error.message, requestId, {
|
|
100
|
-
cause: error.cause,
|
|
101
|
-
});
|
|
102
|
-
throw error;
|
|
103
|
-
}
|
|
104
|
-
finally {
|
|
105
|
-
clearTimeout(timer);
|
|
106
|
-
this.discoveryControllers.delete(controller);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
48
|
async close() {
|
|
110
49
|
if (!this.closing) {
|
|
111
50
|
this.closed = true;
|
|
112
51
|
this.closing = (async () => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
52
|
+
try {
|
|
53
|
+
const results = await Promise.allSettled([
|
|
54
|
+
this.ttsClient.close(),
|
|
55
|
+
this.sttClient.close(),
|
|
56
|
+
this.realtimeClient.close(),
|
|
57
|
+
]);
|
|
58
|
+
for (const result of results)
|
|
59
|
+
if (result.status === "rejected")
|
|
60
|
+
throw result.reason;
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await this.credentials.close();
|
|
64
|
+
}
|
|
118
65
|
})();
|
|
119
66
|
}
|
|
120
67
|
await this.closing;
|
package/dist/grpc.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as grpc from "@grpc/grpc-js";
|
|
2
|
+
import { type DescMessage, type MessageShape } from "@bufbuild/protobuf";
|
|
3
|
+
import * as errors from "./errors.js";
|
|
4
|
+
export declare function rpcError(code: number, requestId?: string | null): errors.RimeError;
|
|
5
|
+
export declare function serializer<D extends DescMessage>(desc: D): (message: MessageShape<D>) => Buffer<ArrayBuffer>;
|
|
6
|
+
export declare function deserializer<D extends DescMessage>(desc: D): (data: Buffer) => MessageShape<D>;
|
|
7
|
+
export declare function requestId(metadata?: grpc.Metadata): string | null;
|
|
8
|
+
export declare function nativeError(error: unknown, id: string | null): errors.RimeError;
|
|
9
|
+
export declare class BidiCall<Input, Output> {
|
|
10
|
+
private readonly call;
|
|
11
|
+
private signal;
|
|
12
|
+
private readonly metadataPromise;
|
|
13
|
+
private readonly statusPromise;
|
|
14
|
+
private status;
|
|
15
|
+
private id;
|
|
16
|
+
constructor(call: grpc.ClientDuplexStream<Input, Output>, signal: AbortSignal);
|
|
17
|
+
get requestId(): string | null;
|
|
18
|
+
get done(): boolean;
|
|
19
|
+
cancel(): void;
|
|
20
|
+
private error;
|
|
21
|
+
write(message: Input): Promise<void>;
|
|
22
|
+
finishInput(): void;
|
|
23
|
+
headers(): Promise<grpc.Metadata>;
|
|
24
|
+
responses(): AsyncGenerator<Output>;
|
|
25
|
+
}
|
|
26
|
+
export declare function connect(client: grpc.Client, signal: AbortSignal, seconds: number): Promise<void>;
|
|
27
|
+
export declare function metadata(value: string): grpc.Metadata;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
import { abortable } from "./cancellation.js";
|
|
1
2
|
import * as grpc from "@grpc/grpc-js";
|
|
2
|
-
import {
|
|
3
|
-
import * as schema from "@rimelabs/api";
|
|
4
|
-
import { policy, abortable } from "./policy.js";
|
|
3
|
+
import { fromBinary, toBinary, } from "@bufbuild/protobuf";
|
|
5
4
|
import * as errors from "./errors.js";
|
|
6
5
|
export function rpcError(code, requestId = null) {
|
|
7
6
|
const classes = {
|
|
@@ -16,53 +15,16 @@ export function rpcError(code, requestId = null) {
|
|
|
16
15
|
const ErrorType = classes[code] ?? errors.RimeStreamError;
|
|
17
16
|
return new ErrorType(`Rime operation failed: ${grpc.status[code] ?? "UNKNOWN"}`, requestId);
|
|
18
17
|
}
|
|
19
|
-
export
|
|
20
|
-
makeClient: (target) => new grpc.Client(target, grpc.credentials.createSsl(), {
|
|
21
|
-
"grpc.enable_retries": 0,
|
|
22
|
-
"grpc.max_receive_message_length": policy.receiveBytes,
|
|
23
|
-
"grpc.max_send_message_length": policy.sentenceBytes + 65536,
|
|
24
|
-
}),
|
|
25
|
-
};
|
|
26
|
-
function serializer(desc) {
|
|
18
|
+
export function serializer(desc) {
|
|
27
19
|
return (message) => Buffer.from(toBinary(desc, message));
|
|
28
20
|
}
|
|
29
|
-
function deserializer(desc) {
|
|
21
|
+
export function deserializer(desc) {
|
|
30
22
|
return (data) => fromBinary(desc, data);
|
|
31
23
|
}
|
|
32
|
-
export
|
|
33
|
-
await abortable(new Promise((resolve, reject) => client.waitForReady(Date.now() + policy.connectionTimeout * 1000, (error) => error
|
|
34
|
-
? reject(new errors.RimeTimeoutError("Connection establishment timed out"))
|
|
35
|
-
: resolve())), signal);
|
|
36
|
-
}
|
|
37
|
-
export function metadata(value) {
|
|
38
|
-
const result = new grpc.Metadata();
|
|
39
|
-
result.set("authorization", value);
|
|
40
|
-
return result;
|
|
41
|
-
}
|
|
42
|
-
function openStream(client, auth) {
|
|
43
|
-
return client.makeBidiStreamRequest("/rime.TextToSpeech/SynthesizeStreaming", serializer(schema.StreamingSynthesisRequestSchema), deserializer(schema.SynthesisResponseStreamSchema), auth);
|
|
44
|
-
}
|
|
45
|
-
function header(voice, language) {
|
|
46
|
-
return create(schema.StreamingSynthesisRequestSchema, {
|
|
47
|
-
payload: {
|
|
48
|
-
case: "header",
|
|
49
|
-
value: create(schema.SynthesisRequestSchema, {
|
|
50
|
-
speaker: voice,
|
|
51
|
-
language,
|
|
52
|
-
audioParameters: { audioFormat: "audio/pcm", samplingRate: 24000 },
|
|
53
|
-
}),
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
function textMessage(text) {
|
|
58
|
-
return create(schema.StreamingSynthesisRequestSchema, {
|
|
59
|
-
payload: { case: "textChunk", value: text },
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
function requestId(metadata) {
|
|
24
|
+
export function requestId(metadata) {
|
|
63
25
|
return metadata?.get("x-request-id")[0]?.toString() ?? null;
|
|
64
26
|
}
|
|
65
|
-
function nativeError(error, id) {
|
|
27
|
+
export function nativeError(error, id) {
|
|
66
28
|
if (error instanceof errors.RimeError)
|
|
67
29
|
return error;
|
|
68
30
|
if (error &&
|
|
@@ -72,56 +34,20 @@ function nativeError(error, id) {
|
|
|
72
34
|
const metadata = "metadata" in error ? error.metadata : null;
|
|
73
35
|
return rpcError(error.code, id ?? (metadata instanceof grpc.Metadata ? requestId(metadata) : null));
|
|
74
36
|
}
|
|
75
|
-
return new errors.RimeStreamError("
|
|
37
|
+
return new errors.RimeStreamError("Rime transport failed", id, {
|
|
76
38
|
cause: error,
|
|
77
39
|
});
|
|
78
40
|
}
|
|
79
|
-
|
|
80
|
-
let call;
|
|
81
|
-
let id = null;
|
|
82
|
-
try {
|
|
83
|
-
signal.throwIfAborted();
|
|
84
|
-
const operation = new Promise((resolve, reject) => {
|
|
85
|
-
call = prepared.client.makeUnaryRequest("/rime.TextToSpeech/" + name, serializer(input), deserializer(output), request, prepared.metadata, (error, result) => (error ? reject(error) : resolve(result)));
|
|
86
|
-
call.once("metadata", (metadata) => {
|
|
87
|
-
id = requestId(metadata);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
return await abortable(operation, signal);
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
call?.cancel();
|
|
94
|
-
if (signal.aborted) {
|
|
95
|
-
if (signal.reason instanceof errors.RimeTimeoutError)
|
|
96
|
-
throw new errors.RimeTimeoutError(signal.reason.message, id, {
|
|
97
|
-
cause: signal.reason.cause,
|
|
98
|
-
});
|
|
99
|
-
throw signal.reason;
|
|
100
|
-
}
|
|
101
|
-
throw nativeError(error, id);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
export async function discover(prepared, kind, language, signal) {
|
|
105
|
-
// Exactly one attempt. The client owns retries and the shared deadline.
|
|
106
|
-
if (kind === "voices") {
|
|
107
|
-
const method = schema.TextToSpeech.method.getSupportedSpeakers;
|
|
108
|
-
const response = await unary(prepared, method.name, method.input, method.output, create(method.input, { language: language ?? undefined }), signal);
|
|
109
|
-
return response.speakers;
|
|
110
|
-
}
|
|
111
|
-
const method = schema.TextToSpeech.method.getSupportedLanguages;
|
|
112
|
-
const response = await unary(prepared, method.name, method.input, method.output, create(method.input), signal);
|
|
113
|
-
return response.languages;
|
|
114
|
-
}
|
|
115
|
-
export class SynthesisCall {
|
|
116
|
-
signal;
|
|
41
|
+
export class BidiCall {
|
|
117
42
|
call;
|
|
43
|
+
signal;
|
|
118
44
|
metadataPromise;
|
|
119
45
|
statusPromise;
|
|
120
46
|
status = null;
|
|
121
47
|
id = null;
|
|
122
|
-
constructor(
|
|
48
|
+
constructor(call, signal) {
|
|
49
|
+
this.call = call;
|
|
123
50
|
this.signal = signal;
|
|
124
|
-
this.call = openStream(prepared.client, prepared.metadata);
|
|
125
51
|
this.metadataPromise = new Promise((resolve, reject) => {
|
|
126
52
|
this.call.once("metadata", (metadata) => {
|
|
127
53
|
this.id = requestId(metadata);
|
|
@@ -155,7 +81,7 @@ export class SynthesisCall {
|
|
|
155
81
|
this.id ??= failure.requestId;
|
|
156
82
|
return failure;
|
|
157
83
|
}
|
|
158
|
-
async
|
|
84
|
+
async write(message) {
|
|
159
85
|
try {
|
|
160
86
|
this.signal.throwIfAborted();
|
|
161
87
|
if (this.status) {
|
|
@@ -180,13 +106,6 @@ export class SynthesisCall {
|
|
|
180
106
|
throw this.error(error);
|
|
181
107
|
}
|
|
182
108
|
}
|
|
183
|
-
async start(voice, language) {
|
|
184
|
-
// Writes do not depend on response metadata.
|
|
185
|
-
await this.writeMessage(header(voice, language));
|
|
186
|
-
}
|
|
187
|
-
async write(sentence) {
|
|
188
|
-
await this.writeMessage(textMessage(sentence));
|
|
189
|
-
}
|
|
190
109
|
finishInput() {
|
|
191
110
|
try {
|
|
192
111
|
this.call.end();
|
|
@@ -195,24 +114,25 @@ export class SynthesisCall {
|
|
|
195
114
|
throw this.error(error);
|
|
196
115
|
}
|
|
197
116
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
117
|
+
async headers() {
|
|
118
|
+
try {
|
|
119
|
+
return await abortable(this.metadataPromise, this.signal);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (this.signal.aborted)
|
|
123
|
+
throw this.signal.reason;
|
|
124
|
+
throw this.error(error);
|
|
125
|
+
}
|
|
201
126
|
}
|
|
202
|
-
async *
|
|
127
|
+
async *responses() {
|
|
203
128
|
try {
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
for await (const response of
|
|
207
|
-
|
|
208
|
-
this.checkFormat(contentType);
|
|
209
|
-
yield response.audio;
|
|
210
|
-
}
|
|
129
|
+
await this.headers();
|
|
130
|
+
const responses = this.call;
|
|
131
|
+
for await (const response of responses)
|
|
132
|
+
yield response;
|
|
211
133
|
const status = await abortable(this.statusPromise, this.signal);
|
|
212
134
|
if (status.code !== grpc.status.OK)
|
|
213
135
|
throw rpcError(status.code, this.id);
|
|
214
|
-
// Validate empty success only after giving a rejection its proper error.
|
|
215
|
-
this.checkFormat(contentType);
|
|
216
136
|
}
|
|
217
137
|
catch (error) {
|
|
218
138
|
if (this.signal.aborted)
|
|
@@ -221,3 +141,13 @@ export class SynthesisCall {
|
|
|
221
141
|
}
|
|
222
142
|
}
|
|
223
143
|
}
|
|
144
|
+
export async function connect(client, signal, seconds) {
|
|
145
|
+
await abortable(new Promise((resolve, reject) => client.waitForReady(Date.now() + seconds * 1000, (error) => error
|
|
146
|
+
? reject(new errors.RimeTimeoutError("Connection establishment timed out"))
|
|
147
|
+
: resolve())), signal);
|
|
148
|
+
}
|
|
149
|
+
export function metadata(value) {
|
|
150
|
+
const result = new grpc.Metadata();
|
|
151
|
+
result.set("authorization", value);
|
|
152
|
+
return result;
|
|
153
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export { Rime, type RimeOptions, type SynthesisOptions, type DiscoveryOptions, type VoiceListOptions, } from "./client.js";
|
|
2
|
-
export { AudioStream } from "./stream.js";
|
|
3
|
-
export { AudioFormat } from "./audio.js";
|
|
2
|
+
export { AudioStream } from "./tts/stream.js";
|
|
3
|
+
export { AudioFormat } from "./tts/audio.js";
|
|
4
4
|
export * from "./errors.js";
|
|
5
|
+
export { RealtimeSession } from "./realtime/session.js";
|
|
6
|
+
export * from "./realtime/types.js";
|
|
7
|
+
export { TranscriptStream } from "./stt/stream.js";
|
|
8
|
+
export type { AudioSource, TranscriptionOptions, TranscriptionMode, TranscriptionPartial, TranscriptionFinal, TranscriptionUpdate, } from "./stt/types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { Rime, } from "./client.js";
|
|
2
|
-
export { AudioStream } from "./stream.js";
|
|
3
|
-
export { AudioFormat } from "./audio.js";
|
|
2
|
+
export { AudioStream } from "./tts/stream.js";
|
|
3
|
+
export { AudioFormat } from "./tts/audio.js";
|
|
4
4
|
export * from "./errors.js";
|
|
5
|
+
export { RealtimeSession } from "./realtime/session.js";
|
|
6
|
+
export * from "./realtime/types.js";
|
|
7
|
+
export { TranscriptStream } from "./stt/stream.js";
|
package/dist/pcm.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Headerless signed little-endian PCM16; stereo samples are interleaved. */
|
|
2
|
+
export interface PCMFormat {
|
|
3
|
+
/** Input frames per second; omitted means 16000. */
|
|
4
|
+
readonly sampleRate?: 8000 | 16000 | 24000 | 48000;
|
|
5
|
+
/** Samples per frame; omitted means one (mono). */
|
|
6
|
+
readonly channels?: 1 | 2;
|
|
7
|
+
/** Two-byte signed samples with no file header. */
|
|
8
|
+
readonly encoding?: "pcm_s16le";
|
|
9
|
+
}
|
|
10
|
+
export declare function resolvePCMFormat(input?: PCMFormat): Required<PCMFormat>;
|
|
11
|
+
/** Streaming linear interpolation, matching Python audioop.ratecv with no filter.
|
|
12
|
+
* Integer phase remains bounded and preserves samples across chunk boundaries.
|
|
13
|
+
*/
|
|
14
|
+
export declare class InputConverter {
|
|
15
|
+
readonly format: Required<PCMFormat>;
|
|
16
|
+
private phase;
|
|
17
|
+
private previous;
|
|
18
|
+
constructor(format: Required<PCMFormat>);
|
|
19
|
+
clone(): InputConverter;
|
|
20
|
+
process(input: Uint8Array): Buffer;
|
|
21
|
+
}
|
package/dist/pcm.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { RimeAudioFormatError } from "./errors.js";
|
|
2
|
+
export function resolvePCMFormat(input) {
|
|
3
|
+
if (input !== undefined &&
|
|
4
|
+
(input === null || typeof input !== "object" || Array.isArray(input)))
|
|
5
|
+
throw new RimeAudioFormatError("Input format must be a PCM format object");
|
|
6
|
+
const format = {
|
|
7
|
+
sampleRate: input?.sampleRate ?? 16000,
|
|
8
|
+
channels: input?.channels ?? 1,
|
|
9
|
+
encoding: input?.encoding ?? "pcm_s16le",
|
|
10
|
+
};
|
|
11
|
+
if (![8000, 16000, 24000, 48000].includes(format.sampleRate) ||
|
|
12
|
+
![1, 2].includes(format.channels) ||
|
|
13
|
+
format.encoding !== "pcm_s16le")
|
|
14
|
+
throw new RimeAudioFormatError("Use PCM16 at 8, 16, 24 or 48 kHz, with one or two channels");
|
|
15
|
+
return format;
|
|
16
|
+
}
|
|
17
|
+
/** Streaming linear interpolation, matching Python audioop.ratecv with no filter.
|
|
18
|
+
* Integer phase remains bounded and preserves samples across chunk boundaries.
|
|
19
|
+
*/
|
|
20
|
+
export class InputConverter {
|
|
21
|
+
format;
|
|
22
|
+
phase = -16000;
|
|
23
|
+
previous = 0;
|
|
24
|
+
constructor(format) {
|
|
25
|
+
this.format = format;
|
|
26
|
+
}
|
|
27
|
+
clone() {
|
|
28
|
+
const copy = new InputConverter(this.format);
|
|
29
|
+
copy.phase = this.phase;
|
|
30
|
+
copy.previous = this.previous;
|
|
31
|
+
return copy;
|
|
32
|
+
}
|
|
33
|
+
process(input) {
|
|
34
|
+
const data = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
35
|
+
const samples = [];
|
|
36
|
+
for (let offset = 0; offset < data.length; offset += this.format.channels * 2) {
|
|
37
|
+
const current = this.format.channels === 1
|
|
38
|
+
? data.readInt16LE(offset)
|
|
39
|
+
: Math.floor((data.readInt16LE(offset) + data.readInt16LE(offset + 2)) / 2);
|
|
40
|
+
this.phase += 16000;
|
|
41
|
+
while (this.phase >= 0) {
|
|
42
|
+
samples.push(Math.floor((this.previous * this.phase + current * (16000 - this.phase)) /
|
|
43
|
+
16000));
|
|
44
|
+
this.phase -= this.format.sampleRate;
|
|
45
|
+
}
|
|
46
|
+
this.previous = current;
|
|
47
|
+
}
|
|
48
|
+
const output = Buffer.alloc(samples.length * 2);
|
|
49
|
+
samples.forEach((sample, index) => output.writeInt16LE(sample, index * 2));
|
|
50
|
+
return output;
|
|
51
|
+
}
|
|
52
|
+
}
|