@rimelabs/sdk 0.1.0-alpha.10 → 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 +23 -0
- package/README.md +36 -6
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +5 -2
- package/dist/cancellation.d.ts +5 -0
- package/dist/cancellation.js +36 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.js +21 -5
- package/dist/grpc.d.ts +27 -0
- package/dist/grpc.js +153 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/pcm.d.ts +21 -0
- package/dist/pcm.js +52 -0
- package/dist/realtime/async.d.ts +1 -5
- package/dist/realtime/async.js +1 -36
- package/dist/realtime/audio.d.ts +0 -11
- package/dist/realtime/audio.js +2 -45
- package/dist/realtime/session.js +2 -1
- package/dist/realtime/types.d.ts +2 -6
- 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/tts/client.d.ts +3 -0
- package/dist/tts/client.js +16 -6
- package/dist/tts/policy.d.ts +1 -1
- package/dist/tts/policy.js +3 -26
- package/dist/tts/stream.d.ts +1 -1
- package/dist/tts/stream.js +2 -6
- package/dist/tts/transport.d.ts +3 -11
- package/dist/tts/transport.js +27 -139
- package/dist/validation.d.ts +5 -0
- package/dist/validation.js +28 -0
- package/docs/stt.md +103 -0
- package/docs/tts.md +11 -4
- package/package.json +4 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { PCMFormat } from "../pcm.js";
|
|
2
|
+
/** Formatting intent, for example "flight 247" or "flight two four seven". */
|
|
3
|
+
export type TranscriptionMode = "written" | "verbatim";
|
|
4
|
+
/** One caller-ended utterance of headerless signed PCM16 little-endian audio. */
|
|
5
|
+
export type AudioSource = AsyncIterable<Uint8Array>;
|
|
6
|
+
export interface TranscriptionOptions {
|
|
7
|
+
/** Required BCP-47 tag, passed unchanged to the service (for example "en"). */
|
|
8
|
+
language: string;
|
|
9
|
+
/** Formatting intent; defaults to written and does not guarantee exact tokens. */
|
|
10
|
+
mode?: TranscriptionMode;
|
|
11
|
+
/** Recognition hints passed unchanged and in order, for example ["Super-G"]. */
|
|
12
|
+
contextTerms?: readonly string[];
|
|
13
|
+
/** Defaults to PCM16 little-endian mono 16 kHz; fixed for the utterance. */
|
|
14
|
+
inputFormat?: PCMFormat;
|
|
15
|
+
/** Optional overall deadline in seconds, including source and consumer pauses. */
|
|
16
|
+
timeout?: number | null;
|
|
17
|
+
/** Cancels only this utterance, including a pending connection. */
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
}
|
|
20
|
+
export interface TranscriptionPartial {
|
|
21
|
+
/** A revisable replacement snapshot. */
|
|
22
|
+
readonly kind: "partial";
|
|
23
|
+
/** Complete current text; replace previous text instead of appending it. */
|
|
24
|
+
readonly text: string;
|
|
25
|
+
}
|
|
26
|
+
export interface TranscriptionFinal {
|
|
27
|
+
/** Successful protocol and transport completion. */
|
|
28
|
+
readonly kind: "final";
|
|
29
|
+
/** Complete final text; silence can produce an empty string. */
|
|
30
|
+
readonly text: string;
|
|
31
|
+
/** Canonical BCP-47 tag selected by the service, for example "en". */
|
|
32
|
+
readonly language: string;
|
|
33
|
+
}
|
|
34
|
+
export type TranscriptionUpdate = TranscriptionPartial | TranscriptionFinal;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/tts/client.d.ts
CHANGED
|
@@ -30,6 +30,9 @@ export declare class TTS {
|
|
|
30
30
|
};
|
|
31
31
|
constructor(credentials: Credentials, deployment: Readonly<typeof policy>, defaultTimeout: number | null, checkOpen: () => void);
|
|
32
32
|
private prepare;
|
|
33
|
+
/** Synthesize complete text and return an audio stream. Do not await this call. */
|
|
34
|
+
synthesize(text: string, options?: SynthesisOptions): AudioStream;
|
|
35
|
+
/** Stream text chunks into synthesis and return an audio stream. Do not await this call. */
|
|
33
36
|
stream(text: TextSource, options?: SynthesisOptions): AudioStream;
|
|
34
37
|
private discover;
|
|
35
38
|
close(): Promise<void>;
|
package/dist/tts/client.js
CHANGED
|
@@ -38,14 +38,24 @@ export class TTS {
|
|
|
38
38
|
await connect(this.client, signal);
|
|
39
39
|
return { client: this.client, metadata: metadata(auth) };
|
|
40
40
|
}
|
|
41
|
+
/** Synthesize complete text and return an audio stream. Do not await this call. */
|
|
42
|
+
synthesize(text, options = {}) {
|
|
43
|
+
this.checkOpen();
|
|
44
|
+
if (typeof text !== "string")
|
|
45
|
+
throw new RimeInputError("text must be a string; use stream() for an async text source");
|
|
46
|
+
if (!text.trim())
|
|
47
|
+
throw new RimeInputError("Text must contain non-whitespace characters");
|
|
48
|
+
return this.stream((async function* () {
|
|
49
|
+
yield text;
|
|
50
|
+
})(), options);
|
|
51
|
+
}
|
|
52
|
+
/** Stream text chunks into synthesis and return an audio stream. Do not await this call. */
|
|
41
53
|
stream(text, options = {}) {
|
|
42
54
|
this.checkOpen();
|
|
43
|
-
if (typeof text === "string"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
else if (!text || typeof text[Symbol.asyncIterator] !== "function")
|
|
48
|
-
throw new RimeInputError("text must be a string or an async iterable of strings");
|
|
55
|
+
if (typeof text === "string" ||
|
|
56
|
+
!text ||
|
|
57
|
+
typeof text[Symbol.asyncIterator] !== "function")
|
|
58
|
+
throw new RimeInputError("text must be an async iterable of strings; use synthesize() for a string");
|
|
49
59
|
const voice = options.voice === undefined || options.voice === null
|
|
50
60
|
? this.deployment.defaultVoice
|
|
51
61
|
: nonempty(options.voice, "voice");
|
package/dist/tts/policy.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { timeout } from "../validation.js";
|
|
1
2
|
export declare const policy: {
|
|
2
3
|
target: string;
|
|
3
4
|
audience: string;
|
|
@@ -16,5 +17,4 @@ export declare const policy: {
|
|
|
16
17
|
cleanupTimeout: number;
|
|
17
18
|
};
|
|
18
19
|
export declare function resolve(model: string, endpoint?: string | null): Readonly<typeof policy>;
|
|
19
|
-
export declare function timeout(value: unknown, inherited?: number | null): number | null;
|
|
20
20
|
export declare function nonempty(value: unknown, name: string): string;
|
package/dist/tts/policy.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { endpointAddress } from "../validation.js";
|
|
2
|
+
export { timeout } from "../validation.js";
|
|
1
3
|
import { RimeInputError } from "../errors.js";
|
|
2
4
|
const codaHostname = "coda.api.rime.ai";
|
|
3
5
|
export const policy = {
|
|
@@ -30,32 +32,7 @@ export function resolve(model, endpoint) {
|
|
|
30
32
|
}
|
|
31
33
|
if (endpoint === undefined || endpoint === null)
|
|
32
34
|
return deployment;
|
|
33
|
-
|
|
34
|
-
if (typeof endpoint !== "string" || /\s/.test(endpoint))
|
|
35
|
-
throw new RimeInputError(error);
|
|
36
|
-
const [host, port, extra] = endpoint.split(":");
|
|
37
|
-
if (!host ||
|
|
38
|
-
host.length > 253 ||
|
|
39
|
-
extra !== undefined ||
|
|
40
|
-
host
|
|
41
|
-
.split(".")
|
|
42
|
-
.some((label) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test(label)) ||
|
|
43
|
-
(port !== undefined &&
|
|
44
|
-
(!/^[0-9]{1,5}$/.test(port) || Number(port) < 1 || Number(port) > 65535)))
|
|
45
|
-
throw new RimeInputError(error);
|
|
46
|
-
return {
|
|
47
|
-
...deployment,
|
|
48
|
-
target: `${host.toLowerCase()}:${port === undefined ? 443 : Number(port)}`,
|
|
49
|
-
audience: host.toLowerCase(),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
export function timeout(value, inherited = null) {
|
|
53
|
-
if (value === undefined)
|
|
54
|
-
return inherited;
|
|
55
|
-
if (value !== null &&
|
|
56
|
-
(typeof value !== "number" || !Number.isFinite(value) || value <= 0))
|
|
57
|
-
throw new RimeInputError("timeout must be finite positive seconds or null");
|
|
58
|
-
return value;
|
|
35
|
+
return { ...deployment, ...endpointAddress(endpoint) };
|
|
59
36
|
}
|
|
60
37
|
export function nonempty(value, name) {
|
|
61
38
|
if (typeof value !== "string" || !value.trim())
|
package/dist/tts/stream.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface StreamOwner {
|
|
|
5
5
|
checkOpen(): void;
|
|
6
6
|
forget(stream: AudioStream): void;
|
|
7
7
|
}
|
|
8
|
-
export type TextSource =
|
|
8
|
+
export type TextSource = AsyncIterable<string>;
|
|
9
9
|
export declare const constructionKey: unique symbol;
|
|
10
10
|
export declare class AudioStream implements AsyncIterableIterator<Uint8Array> {
|
|
11
11
|
private owner;
|
package/dist/tts/stream.js
CHANGED
|
@@ -41,7 +41,7 @@ export class AudioStream {
|
|
|
41
41
|
this.formatValue = formatValue;
|
|
42
42
|
this.timeout = timeout;
|
|
43
43
|
if (key !== constructionKey)
|
|
44
|
-
throw new TypeError("AudioStream is returned by client.tts.stream()");
|
|
44
|
+
throw new TypeError("AudioStream is returned by client.tts.synthesize() or client.tts.stream()");
|
|
45
45
|
}
|
|
46
46
|
get format() {
|
|
47
47
|
return this.formatValue;
|
|
@@ -100,11 +100,7 @@ export class AudioStream {
|
|
|
100
100
|
let meaningful = false;
|
|
101
101
|
try {
|
|
102
102
|
try {
|
|
103
|
-
iterator = (
|
|
104
|
-
? (async function* () {
|
|
105
|
-
yield source;
|
|
106
|
-
})()
|
|
107
|
-
: source)[Symbol.asyncIterator]();
|
|
103
|
+
iterator = source[Symbol.asyncIterator]();
|
|
108
104
|
}
|
|
109
105
|
catch (error) {
|
|
110
106
|
if (this.controller.signal.aborted)
|
package/dist/tts/transport.d.ts
CHANGED
|
@@ -1,31 +1,23 @@
|
|
|
1
|
+
export { metadata } from "../grpc.js";
|
|
2
|
+
export { rpcError } from "../grpc.js";
|
|
1
3
|
import * as grpc from "@grpc/grpc-js";
|
|
2
|
-
import * as errors from "../errors.js";
|
|
3
4
|
export type Connection = grpc.Client;
|
|
4
5
|
export interface PreparedConnection {
|
|
5
6
|
client: Connection;
|
|
6
7
|
metadata: grpc.Metadata;
|
|
7
8
|
}
|
|
8
9
|
export type DiscoveryKind = "voices" | "languages";
|
|
9
|
-
export declare function rpcError(code: number, requestId?: string | null): errors.RimeError;
|
|
10
10
|
export declare const transport: {
|
|
11
11
|
makeClient: (target: string) => grpc.Client;
|
|
12
12
|
};
|
|
13
13
|
export declare function connect(client: grpc.Client, signal: AbortSignal): Promise<void>;
|
|
14
|
-
export declare function metadata(value: string): grpc.Metadata;
|
|
15
14
|
export declare function discover(prepared: PreparedConnection, kind: DiscoveryKind, language: string | null | undefined, signal: AbortSignal): Promise<string[]>;
|
|
16
15
|
export declare class SynthesisCall {
|
|
17
|
-
private
|
|
18
|
-
private readonly call;
|
|
19
|
-
private readonly metadataPromise;
|
|
20
|
-
private readonly statusPromise;
|
|
21
|
-
private status;
|
|
22
|
-
private id;
|
|
16
|
+
private readonly rpc;
|
|
23
17
|
constructor(prepared: PreparedConnection, signal: AbortSignal);
|
|
24
18
|
get requestId(): string | null;
|
|
25
19
|
get done(): boolean;
|
|
26
20
|
cancel(): void;
|
|
27
|
-
private error;
|
|
28
|
-
private writeMessage;
|
|
29
21
|
start(voice: string, language: string): Promise<void>;
|
|
30
22
|
write(sentence: string): Promise<void>;
|
|
31
23
|
finishInput(): void;
|
package/dist/tts/transport.js
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
|
+
import { connect as connectGrpc } from "../grpc.js";
|
|
2
|
+
export { metadata } from "../grpc.js";
|
|
3
|
+
import { BidiCall, serializer, deserializer, requestId, nativeError, } from "../grpc.js";
|
|
4
|
+
export { rpcError } from "../grpc.js";
|
|
1
5
|
import * as grpc from "@grpc/grpc-js";
|
|
2
|
-
import { create,
|
|
6
|
+
import { create, } from "@bufbuild/protobuf";
|
|
3
7
|
import * as schema from "@rimelabs/api";
|
|
4
8
|
import { policy } from "./policy.js";
|
|
5
9
|
import { abortable } from "../cancellation.js";
|
|
6
10
|
import * as errors from "../errors.js";
|
|
7
|
-
export function rpcError(code, requestId = null) {
|
|
8
|
-
const classes = {
|
|
9
|
-
[grpc.status.UNAUTHENTICATED]: errors.RimeAuthenticationError,
|
|
10
|
-
[grpc.status.PERMISSION_DENIED]: errors.RimePermissionError,
|
|
11
|
-
[grpc.status.INVALID_ARGUMENT]: errors.RimeInputError,
|
|
12
|
-
[grpc.status.RESOURCE_EXHAUSTED]: errors.RimeResourceLimitError,
|
|
13
|
-
[grpc.status.UNAVAILABLE]: errors.RimeUnavailableError,
|
|
14
|
-
[grpc.status.DEADLINE_EXCEEDED]: errors.RimeTimeoutError,
|
|
15
|
-
[grpc.status.CANCELLED]: errors.RimeCancelledError,
|
|
16
|
-
};
|
|
17
|
-
const ErrorType = classes[code] ?? errors.RimeStreamError;
|
|
18
|
-
return new ErrorType(`Rime operation failed: ${grpc.status[code] ?? "UNKNOWN"}`, requestId);
|
|
19
|
-
}
|
|
20
11
|
export const transport = {
|
|
21
12
|
makeClient: (target) => new grpc.Client(target, grpc.credentials.createSsl(), {
|
|
22
13
|
"grpc.enable_retries": 0,
|
|
@@ -24,21 +15,8 @@ export const transport = {
|
|
|
24
15
|
"grpc.max_send_message_length": policy.sentenceBytes + 65536,
|
|
25
16
|
}),
|
|
26
17
|
};
|
|
27
|
-
function
|
|
28
|
-
return (
|
|
29
|
-
}
|
|
30
|
-
function deserializer(desc) {
|
|
31
|
-
return (data) => fromBinary(desc, data);
|
|
32
|
-
}
|
|
33
|
-
export async function connect(client, signal) {
|
|
34
|
-
await abortable(new Promise((resolve, reject) => client.waitForReady(Date.now() + policy.connectionTimeout * 1000, (error) => error
|
|
35
|
-
? reject(new errors.RimeTimeoutError("Connection establishment timed out"))
|
|
36
|
-
: resolve())), signal);
|
|
37
|
-
}
|
|
38
|
-
export function metadata(value) {
|
|
39
|
-
const result = new grpc.Metadata();
|
|
40
|
-
result.set("authorization", value);
|
|
41
|
-
return result;
|
|
18
|
+
export function connect(client, signal) {
|
|
19
|
+
return connectGrpc(client, signal, policy.connectionTimeout);
|
|
42
20
|
}
|
|
43
21
|
function openStream(client, auth) {
|
|
44
22
|
return client.makeBidiStreamRequest("/rime.TextToSpeech/SynthesizeStreaming", serializer(schema.StreamingSynthesisRequestSchema), deserializer(schema.SynthesisResponseStreamSchema), auth);
|
|
@@ -60,23 +38,6 @@ function textMessage(text) {
|
|
|
60
38
|
payload: { case: "textChunk", value: text },
|
|
61
39
|
});
|
|
62
40
|
}
|
|
63
|
-
function requestId(metadata) {
|
|
64
|
-
return metadata?.get("x-request-id")[0]?.toString() ?? null;
|
|
65
|
-
}
|
|
66
|
-
function nativeError(error, id) {
|
|
67
|
-
if (error instanceof errors.RimeError)
|
|
68
|
-
return error;
|
|
69
|
-
if (error &&
|
|
70
|
-
typeof error === "object" &&
|
|
71
|
-
"code" in error &&
|
|
72
|
-
typeof error.code === "number") {
|
|
73
|
-
const metadata = "metadata" in error ? error.metadata : null;
|
|
74
|
-
return rpcError(error.code, id ?? (metadata instanceof grpc.Metadata ? requestId(metadata) : null));
|
|
75
|
-
}
|
|
76
|
-
return new errors.RimeStreamError("Rime transport failed", id, {
|
|
77
|
-
cause: error,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
41
|
async function unary(prepared, name, input, output, request, signal) {
|
|
81
42
|
let call;
|
|
82
43
|
let id = null;
|
|
@@ -114,115 +75,42 @@ export async function discover(prepared, kind, language, signal) {
|
|
|
114
75
|
return response.languages;
|
|
115
76
|
}
|
|
116
77
|
export class SynthesisCall {
|
|
117
|
-
|
|
118
|
-
call;
|
|
119
|
-
metadataPromise;
|
|
120
|
-
statusPromise;
|
|
121
|
-
status = null;
|
|
122
|
-
id = null;
|
|
78
|
+
rpc;
|
|
123
79
|
constructor(prepared, signal) {
|
|
124
|
-
this.
|
|
125
|
-
this.call = openStream(prepared.client, prepared.metadata);
|
|
126
|
-
this.metadataPromise = new Promise((resolve, reject) => {
|
|
127
|
-
this.call.once("metadata", (metadata) => {
|
|
128
|
-
this.id = requestId(metadata);
|
|
129
|
-
resolve(metadata);
|
|
130
|
-
});
|
|
131
|
-
// A trailers-only response has no metadata event.
|
|
132
|
-
this.call.once("status", () => resolve(new grpc.Metadata()));
|
|
133
|
-
this.call.once("error", reject);
|
|
134
|
-
});
|
|
135
|
-
this.metadataPromise.catch(() => { });
|
|
136
|
-
this.statusPromise = new Promise((resolve) => {
|
|
137
|
-
this.call.once("status", (status) => {
|
|
138
|
-
this.status = status;
|
|
139
|
-
this.id ??= requestId(status.metadata);
|
|
140
|
-
resolve(status);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
this.call.on("error", () => { });
|
|
80
|
+
this.rpc = new BidiCall(openStream(prepared.client, prepared.metadata), signal);
|
|
144
81
|
}
|
|
145
82
|
get requestId() {
|
|
146
|
-
return this.
|
|
83
|
+
return this.rpc.requestId;
|
|
147
84
|
}
|
|
148
85
|
get done() {
|
|
149
|
-
return this.
|
|
86
|
+
return this.rpc.done;
|
|
150
87
|
}
|
|
151
88
|
cancel() {
|
|
152
|
-
this.
|
|
153
|
-
}
|
|
154
|
-
error(error) {
|
|
155
|
-
const failure = nativeError(error, this.id);
|
|
156
|
-
this.id ??= failure.requestId;
|
|
157
|
-
return failure;
|
|
89
|
+
this.rpc.cancel();
|
|
158
90
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this.signal.throwIfAborted();
|
|
162
|
-
if (this.status) {
|
|
163
|
-
if (this.status.code !== grpc.status.OK)
|
|
164
|
-
throw rpcError(this.status.code, this.id);
|
|
165
|
-
throw new errors.RimeStreamError("The service completed before input finished", this.id);
|
|
166
|
-
}
|
|
167
|
-
await abortable(new Promise((resolve, reject) => {
|
|
168
|
-
this.call.write(message, (error) => error ? reject(error) : resolve());
|
|
169
|
-
}), this.signal);
|
|
170
|
-
}
|
|
171
|
-
catch (error) {
|
|
172
|
-
if (this.signal.aborted)
|
|
173
|
-
throw this.signal.reason;
|
|
174
|
-
if (error instanceof errors.RimeError)
|
|
175
|
-
throw error;
|
|
176
|
-
// A native write error can precede the final status event. Preserve the
|
|
177
|
-
// server rejection even if the reader has not consumed a response.
|
|
178
|
-
const status = await abortable(this.statusPromise, this.signal);
|
|
179
|
-
if (status.code !== grpc.status.OK)
|
|
180
|
-
throw rpcError(status.code, this.id);
|
|
181
|
-
throw this.error(error);
|
|
182
|
-
}
|
|
91
|
+
start(voice, language) {
|
|
92
|
+
return this.rpc.write(header(voice, language));
|
|
183
93
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
await this.writeMessage(header(voice, language));
|
|
187
|
-
}
|
|
188
|
-
async write(sentence) {
|
|
189
|
-
await this.writeMessage(textMessage(sentence));
|
|
94
|
+
write(sentence) {
|
|
95
|
+
return this.rpc.write(textMessage(sentence));
|
|
190
96
|
}
|
|
191
97
|
finishInput() {
|
|
192
|
-
|
|
193
|
-
this.call.end();
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
throw this.error(error);
|
|
197
|
-
}
|
|
98
|
+
this.rpc.finishInput();
|
|
198
99
|
}
|
|
199
100
|
checkFormat(contentType) {
|
|
200
101
|
if (contentType !== "audio/pcm")
|
|
201
|
-
throw new errors.RimeAudioFormatError("Expected raw audio/pcm from the service", this.
|
|
102
|
+
throw new errors.RimeAudioFormatError("Expected raw audio/pcm from the service", this.requestId);
|
|
202
103
|
}
|
|
203
104
|
async *audio() {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (audio.length)
|
|
213
|
-
this.checkFormat(contentType);
|
|
214
|
-
yield audio;
|
|
215
|
-
}
|
|
216
|
-
const status = await abortable(this.statusPromise, this.signal);
|
|
217
|
-
if (status.code !== grpc.status.OK)
|
|
218
|
-
throw rpcError(status.code, this.id);
|
|
219
|
-
// Validate empty success only after giving a rejection its proper error.
|
|
220
|
-
this.checkFormat(contentType);
|
|
221
|
-
}
|
|
222
|
-
catch (error) {
|
|
223
|
-
if (this.signal.aborted)
|
|
224
|
-
throw this.signal.reason;
|
|
225
|
-
throw this.error(error);
|
|
105
|
+
const headers = await this.rpc.headers();
|
|
106
|
+
const contentType = headers.get("x-rime-audio-content-type")[0];
|
|
107
|
+
for await (const response of this.rpc.responses()) {
|
|
108
|
+
if (response.payload.case !== "audio")
|
|
109
|
+
continue;
|
|
110
|
+
if (response.payload.value.length)
|
|
111
|
+
this.checkFormat(contentType);
|
|
112
|
+
yield response.payload.value;
|
|
226
113
|
}
|
|
114
|
+
this.checkFormat(contentType);
|
|
227
115
|
}
|
|
228
116
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RimeInputError } from "./errors.js";
|
|
2
|
+
export function endpointAddress(endpoint) {
|
|
3
|
+
const error = "endpoint must be a hostname with an optional port (1-65535), without a scheme or path";
|
|
4
|
+
if (typeof endpoint !== "string" || /\s/.test(endpoint))
|
|
5
|
+
throw new RimeInputError(error);
|
|
6
|
+
const [host, port, extra] = endpoint.split(":");
|
|
7
|
+
if (!host ||
|
|
8
|
+
host.length > 253 ||
|
|
9
|
+
extra !== undefined ||
|
|
10
|
+
host
|
|
11
|
+
.split(".")
|
|
12
|
+
.some((label) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test(label)) ||
|
|
13
|
+
(port !== undefined &&
|
|
14
|
+
(!/^[0-9]{1,5}$/.test(port) || Number(port) < 1 || Number(port) > 65535)))
|
|
15
|
+
throw new RimeInputError(error);
|
|
16
|
+
return {
|
|
17
|
+
target: `${host.toLowerCase()}:${port === undefined ? 443 : Number(port)}`,
|
|
18
|
+
audience: host.toLowerCase(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function timeout(value, inherited = null) {
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
return inherited;
|
|
24
|
+
if (value !== null &&
|
|
25
|
+
(typeof value !== "number" || !Number.isFinite(value) || value <= 0))
|
|
26
|
+
throw new RimeInputError("timeout must be finite positive seconds or null");
|
|
27
|
+
return value;
|
|
28
|
+
}
|
package/docs/stt.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Streaming speech recognition
|
|
2
|
+
|
|
3
|
+
`client.stt.stream` transcribes one utterance from an async source of `Uint8Array` chunks (including Node buffers).
|
|
4
|
+
The SDK handles the gRPC protocol, authentication, audio conversion, transcript
|
|
5
|
+
revisions, backpressure, and cancellation. Your application decides when speech
|
|
6
|
+
ends by exhausting the source.
|
|
7
|
+
|
|
8
|
+
```typescript
|
|
9
|
+
import { createReadStream } from "node:fs";
|
|
10
|
+
import { Rime } from "@rimelabs/sdk";
|
|
11
|
+
|
|
12
|
+
async function* audioChunks() {
|
|
13
|
+
yield* createReadStream("utterance.pcm");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const client = new Rime();
|
|
17
|
+
try {
|
|
18
|
+
const transcript = client.stt.stream(audioChunks(), {
|
|
19
|
+
language: "en",
|
|
20
|
+
inputFormat: { sampleRate: 16000, channels: 1 },
|
|
21
|
+
mode: "written",
|
|
22
|
+
contextTerms: ["Super-G", "USOC"],
|
|
23
|
+
timeout: 120,
|
|
24
|
+
});
|
|
25
|
+
for await (const update of transcript) {
|
|
26
|
+
if (update.kind === "partial") console.log("Current transcript:", update.text);
|
|
27
|
+
else console.log("Final:", update.text, "Language:", update.language);
|
|
28
|
+
}
|
|
29
|
+
console.log("Request:", transcript.requestId);
|
|
30
|
+
} finally {
|
|
31
|
+
await client.close();
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The runnable [example](../examples/stt/stream.mjs) reads a PCM file
|
|
36
|
+
without loading it all into memory. Use Node.js 22 or later; browsers are not supported.
|
|
37
|
+
Open files inside the async generator so file errors are reported through the
|
|
38
|
+
SDK and resources are opened only after the service accepts the utterance.
|
|
39
|
+
|
|
40
|
+
## Options
|
|
41
|
+
|
|
42
|
+
| Option | Meaning |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `language` | Required spoken BCP-47 tag. Passed unchanged to the service; no detection or SDK language allowlist. The deployed catalog currently supports English and Spanish. |
|
|
45
|
+
| `mode` | `"written"` (default) or `"verbatim"`. Formatting intent, not a guarantee of exact tokens. |
|
|
46
|
+
| `contextTerms` | Sequence of recognition hints, copied unchanged and in order. The service validates terms and prompt budgets. |
|
|
47
|
+
| `inputFormat` | `PCMFormat`: signed little-endian PCM16 at 8, 16, 24 or 48 kHz, mono or interleaved stereo. Defaults to 16 kHz mono. |
|
|
48
|
+
| `timeout` | Optional positive overall timeout in seconds. Includes connection time, source pauses and consumer pauses. `null` disables this SDK deadline, but not internal or server deadlines. |
|
|
49
|
+
|
|
50
|
+
Input contains raw samples, with no WAV header or compressed codec. Chunks may
|
|
51
|
+
split sample frames; the SDK carries incomplete bytes into the next chunk. A
|
|
52
|
+
truncated final frame raises `RimeAudioFormatError`, without committing the
|
|
53
|
+
utterance. Each gRPC audio payload is at most 64 KiB of 16 kHz mono PCM16.
|
|
54
|
+
|
|
55
|
+
The default recognition endpoint is `stt.api.rime.ai:443`, using TLS and the same
|
|
56
|
+
API key as the other features. `new Rime({ sttEndpoint: "host:443" })` overrides only STT.
|
|
57
|
+
The existing `model`, `endpoint` and client `timeout` settings remain TTS settings.
|
|
58
|
+
There is no STT voice/model selector; the service selects its recognizer.
|
|
59
|
+
|
|
60
|
+
## Results and completion
|
|
61
|
+
|
|
62
|
+
`TranscriptionPartial` and `TranscriptionFinal` are discriminated TypeScript types, exported from `@rimelabs/sdk`. Every update contains the complete current `text`.
|
|
63
|
+
**Replace the previous transcript. Do not concatenate partials.** Later partials
|
|
64
|
+
can change earlier words. `TranscriptionFinal.language` is the canonical language
|
|
65
|
+
selected by the service.
|
|
66
|
+
|
|
67
|
+
The source is not consumed until the server accepts the configuration. Audio
|
|
68
|
+
upload and transcript reading run concurrently. Decoding currently uses cumulative
|
|
69
|
+
two-second audio windows, so a partial is not promised for each audio packet.
|
|
70
|
+
Silence can finish successfully with empty final text and no partials.
|
|
71
|
+
|
|
72
|
+
Exhausting the source half-closes gRPC input. The SDK continues reading and emits
|
|
73
|
+
one final result only after a matching `done` and successful gRPC completion.
|
|
74
|
+
EOF, an error status, or cancellation cannot turn a partial into a final result.
|
|
75
|
+
No audio is automatically replayed after a connection failure.
|
|
76
|
+
|
|
77
|
+
## Lifetime and limits
|
|
78
|
+
|
|
79
|
+
Construction is lazy: starting iteration begins work. Consume each stream with
|
|
80
|
+
one reader. Breaking a `for await` loop calls the iterator's `return` and cancels
|
|
81
|
+
unfinished work. `await transcript.cancel()` and async disposal also cancel it.
|
|
82
|
+
Pass an `AbortSignal` through the stream's `signal` option to cancel just that
|
|
83
|
+
utterance. Client close cancels all STT, TTS and realtime operations; cancelling
|
|
84
|
+
one stream leaves its siblings running.
|
|
85
|
+
|
|
86
|
+
The SDK bounds the transcript queue to 16 updates, each with at most 64 KiB of
|
|
87
|
+
UTF-8 text, and bounds gRPC responses to 256 KiB. Slow consumers apply backpressure.
|
|
88
|
+
Internal connection and acceptance waits are 10 seconds; completion after source
|
|
89
|
+
exhaustion is bounded to 120 seconds. The completion timer stops after successful
|
|
90
|
+
gRPC completion. The optional overall timeout remains active until the final
|
|
91
|
+
result is consumed. An outstanding source `next()` cannot be forcibly stopped by
|
|
92
|
+
JavaScript; the SDK stops waiting for it and attempts `return()` for at most two
|
|
93
|
+
seconds. Sources that own devices should support cancellation themselves.
|
|
94
|
+
|
|
95
|
+
Server input-idle, utterance-size, request-duration and model-context limits still
|
|
96
|
+
apply. The current wire limit is 16 MiB per utterance; model context can impose a
|
|
97
|
+
smaller limit depending on audio and recognition terms. There is no SDK VAD,
|
|
98
|
+
server endpointing, word timing, diarization, or file-format detection.
|
|
99
|
+
|
|
100
|
+
Failures use the shared SDK error hierarchy: caller/input errors, cancellation,
|
|
101
|
+
authentication, permission, resource limits, timeouts, transient availability and
|
|
102
|
+
protocol failures stay distinct. `requestId` is retained on streams and failures
|
|
103
|
+
when the server supplies it. Keep it when reporting a problem.
|
package/docs/tts.md
CHANGED
|
@@ -4,9 +4,16 @@ You supply text; the SDK handles sentence boundaries and streams audio back.
|
|
|
4
4
|
Your application owns playback. Start with the complete WAV example in the
|
|
5
5
|
[package quick start](../README.md#tts-save-speech).
|
|
6
6
|
|
|
7
|
+
## Complete text
|
|
8
|
+
|
|
9
|
+
Use `client.tts.synthesize(text)` for one nonblank string. It returns an audio
|
|
10
|
+
stream; it does not wait for all audio to be generated. Do not await this call.
|
|
11
|
+
Use the audio loop and cleanup shown in the package quick start.
|
|
12
|
+
|
|
7
13
|
## Stream incoming text
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
Use `client.tts.stream(chunks)` for an async iterable of strings. A plain string
|
|
16
|
+
belongs in `synthesize`, not `stream`. The source can supply partial sentences:
|
|
10
17
|
|
|
11
18
|
```javascript
|
|
12
19
|
async function* text() {
|
|
@@ -45,8 +52,8 @@ Model defaults still apply.
|
|
|
45
52
|
|
|
46
53
|
### Synthesis options
|
|
47
54
|
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
Both methods require text input: a nonblank string for `synthesize`, or an
|
|
56
|
+
async iterable of strings for `stream`. Both accept the following optional settings:
|
|
50
57
|
|
|
51
58
|
| Option | Default | Meaning |
|
|
52
59
|
| --- | --- | --- |
|
|
@@ -97,7 +104,7 @@ import { Rime, RimeError } from "@rimelabs/sdk";
|
|
|
97
104
|
let client;
|
|
98
105
|
try {
|
|
99
106
|
client = new Rime();
|
|
100
|
-
await writeFile("speech.pcm", client.tts.
|
|
107
|
+
await writeFile("speech.pcm", client.tts.synthesize("Hello. This is Rime."));
|
|
101
108
|
} catch (error) {
|
|
102
109
|
if (error instanceof RimeError) {
|
|
103
110
|
console.error(`Speech failed: ${error.message}; requestId=${error.requestId}`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelabs/sdk",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"description": "Rime streaming TTS and Prism realtime SDK for Node.js",
|
|
3
|
+
"version": "0.1.0-alpha.12",
|
|
4
|
+
"description": "Rime streaming TTS, STT and Prism realtime SDK for Node.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
30
30
|
"test": "npm run build && node --test tests/*.test.mjs",
|
|
31
31
|
"prepack": "npm run build",
|
|
32
|
-
"lint": "prettier --check src tests
|
|
32
|
+
"lint": "prettier --check src tests examples/tts examples/realtime examples/common examples/browser examples/tests examples/stt"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@bufbuild/protobuf": "^2.11.0",
|
|
36
36
|
"@grpc/grpc-js": "^1.14.0",
|
|
37
|
-
"@rimelabs/api": "0.
|
|
37
|
+
"@rimelabs/api": "0.4.0",
|
|
38
38
|
"ws": "^8.22.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|