@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.0-alpha.12](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.11...typescript-v0.1.0-alpha.12) (2026-10-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### ⚠ BREAKING CHANGES
|
|
7
|
+
|
|
8
|
+
* Use synthesize for complete text and stream for incremental text in all SDKs. Go StreamSource becomes Stream. Rust synthesis moves under Client::tts(). Both methods return streaming audio.
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* align TTS APIs and SDK example workflows ([#49](https://github.com/rimelabs/rime-sdk/issues/49)) ([bc2be77](https://github.com/rimelabs/rime-sdk/commit/bc2be779f7a845d51850fbe068a4cf6d95eab666))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **deps:** bump @rimelabs/api from 0.3.0 to 0.4.0 ([#54](https://github.com/rimelabs/rime-sdk/issues/54)) ([bc89e38](https://github.com/rimelabs/rime-sdk/commit/bc89e384a6555ed8bd31b94f62c1647662543769))
|
|
18
|
+
|
|
19
|
+
## [0.1.0-alpha.11](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.10...typescript-v0.1.0-alpha.11) (2026-10-09)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* add streaming STT to Python, Node.js, and Go ([#39](https://github.com/rimelabs/rime-sdk/issues/39)) ([79977ef](https://github.com/rimelabs/rime-sdk/commit/79977ef019bc474d799881e60223c6868af104d0))
|
|
25
|
+
|
|
3
26
|
## [0.1.0-alpha.10](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.9...typescript-v0.1.0-alpha.10) (2026-10-08)
|
|
4
27
|
|
|
5
28
|
|
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Rime SDK for Node.js
|
|
2
2
|
|
|
3
|
-
Generate speech with TTS, or send speech to Prism
|
|
4
|
-
|
|
3
|
+
Generate speech with TTS, transcribe audio with STT, or send speech to Prism
|
|
4
|
+
and receive a spoken reply. All use the `Rime` client. Your application handles
|
|
5
|
+
audio capture and playback.
|
|
5
6
|
|
|
6
7
|
## Install
|
|
7
8
|
|
|
@@ -36,7 +37,7 @@ import { Rime } from "@rimelabs/sdk";
|
|
|
36
37
|
const client = new Rime({ timeout: 60 });
|
|
37
38
|
try {
|
|
38
39
|
const chunks = [];
|
|
39
|
-
for await (const chunk of client.tts.
|
|
40
|
+
for await (const chunk of client.tts.synthesize(
|
|
40
41
|
"Your appointment is confirmed for tomorrow.",
|
|
41
42
|
)) {
|
|
42
43
|
chunks.push(Buffer.from(chunk));
|
|
@@ -59,10 +60,39 @@ This short example collects the audio in memory before writing the file.
|
|
|
59
60
|
For long responses, write audio chunks as they arrive.
|
|
60
61
|
|
|
61
62
|
Coda is the default model. Use `new Rime({ model: "mistv3" })` for Mist v3, or pass
|
|
62
|
-
`{ voice: "your-voice" }` as the second argument to `client.tts.
|
|
63
|
+
`{ voice: "your-voice" }` as the second argument to `client.tts.synthesize(...)` to select a voice.
|
|
63
64
|
The default output is mono 24 kHz PCM16. The example adds its WAV header.
|
|
64
|
-
To supply text from an LLM,
|
|
65
|
-
|
|
65
|
+
Use `client.tts.synthesize(text)` for a complete string. To supply text from an LLM,
|
|
66
|
+
pass an async iterable of strings to `client.tts.stream(chunks)`. Both methods
|
|
67
|
+
return streaming audio. The SDK handles sentence boundaries.
|
|
68
|
+
|
|
69
|
+
## STT quick start
|
|
70
|
+
|
|
71
|
+
Stream one utterance of headerless signed PCM16 little-endian audio. Specify the
|
|
72
|
+
spoken language; exhaust the audio source when the utterance ends. Each update
|
|
73
|
+
replaces the complete current transcript.
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
import { createReadStream } from "node:fs";
|
|
77
|
+
import { Rime } from "@rimelabs/sdk";
|
|
78
|
+
|
|
79
|
+
async function* audioChunks() {
|
|
80
|
+
yield* createReadStream("utterance.pcm");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const client = new Rime();
|
|
84
|
+
try {
|
|
85
|
+
const transcript = client.stt.stream(audioChunks(), {
|
|
86
|
+
language: "en",
|
|
87
|
+
});
|
|
88
|
+
for await (const update of transcript) console.log(update.kind, update.text);
|
|
89
|
+
} finally {
|
|
90
|
+
await client.close();
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Input defaults to 16 kHz mono. The [STT guide](https://github.com/rimelabs/rime-sdk/blob/main/typescript/docs/stt.md)
|
|
95
|
+
covers other PCM formats, written/verbatim output, recognition hints and cancellation.
|
|
66
96
|
|
|
67
97
|
## Prism: send recorded speech
|
|
68
98
|
|
package/dist/auth.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare class Credentials {
|
|
|
21
21
|
private controller;
|
|
22
22
|
private closed;
|
|
23
23
|
constructor(key: string, configuration: Readonly<AuthenticationConfiguration>);
|
|
24
|
+
authorization(): string;
|
|
24
25
|
realtimeAuthorization(): string;
|
|
25
26
|
metadata(signal: AbortSignal): Promise<string>;
|
|
26
27
|
themisMetadata(signal: AbortSignal): Promise<string>;
|
package/dist/auth.js
CHANGED
|
@@ -81,18 +81,21 @@ export class Credentials {
|
|
|
81
81
|
this.key = key;
|
|
82
82
|
this.configuration = configuration;
|
|
83
83
|
}
|
|
84
|
-
|
|
84
|
+
authorization() {
|
|
85
85
|
if (this.closed)
|
|
86
86
|
throw new RimeAuthenticationError("Credentials are closed");
|
|
87
87
|
return `Bearer ${this.key}`;
|
|
88
88
|
}
|
|
89
|
+
realtimeAuthorization() {
|
|
90
|
+
return this.authorization();
|
|
91
|
+
}
|
|
89
92
|
async metadata(signal) {
|
|
90
93
|
// TEMPORARY until Themis is ready: replace this body with the call below.
|
|
91
94
|
// return this.themisMetadata(signal);
|
|
92
95
|
if (this.closed)
|
|
93
96
|
throw new RimeAuthenticationError("Credentials are closed");
|
|
94
97
|
signal.throwIfAborted();
|
|
95
|
-
return
|
|
98
|
+
return this.authorization();
|
|
96
99
|
}
|
|
97
100
|
async themisMetadata(signal) {
|
|
98
101
|
if (this.closed)
|
package/dist/cancellation.d.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
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
|
+
};
|
package/dist/cancellation.js
CHANGED
|
@@ -16,3 +16,39 @@ export function abortable(promise, signal) {
|
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
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,3 +1,5 @@
|
|
|
1
|
+
import type { AudioSource, TranscriptionOptions } from "./stt/types.js";
|
|
2
|
+
import type { TranscriptStream } from "./stt/stream.js";
|
|
1
3
|
import { type SynthesisOptions, type DiscoveryOptions, type VoiceListOptions } from "./tts/client.js";
|
|
2
4
|
import { type AudioStream, type TextSource } from "./tts/stream.js";
|
|
3
5
|
import type { RealtimeConnectOptions } from "./realtime/types.js";
|
|
@@ -9,10 +11,16 @@ export interface RimeOptions {
|
|
|
9
11
|
model?: string;
|
|
10
12
|
endpoint?: string | null;
|
|
11
13
|
timeout?: number | null;
|
|
14
|
+
/** Recognition hostname and optional port; defaults to stt.api.rime.ai:443. */
|
|
15
|
+
sttEndpoint?: string | null;
|
|
12
16
|
}
|
|
13
17
|
export declare class Rime {
|
|
14
18
|
private readonly credentials;
|
|
15
19
|
private readonly ttsClient;
|
|
20
|
+
private readonly sttClient;
|
|
21
|
+
readonly stt: {
|
|
22
|
+
stream: (audio: AudioSource, options: TranscriptionOptions) => TranscriptStream;
|
|
23
|
+
};
|
|
16
24
|
private readonly realtimeClient;
|
|
17
25
|
readonly realtime: {
|
|
18
26
|
connect: (options: RealtimeConnectOptions) => Promise<RealtimeSession>;
|
|
@@ -20,6 +28,9 @@ export declare class Rime {
|
|
|
20
28
|
private closed;
|
|
21
29
|
private closing;
|
|
22
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. */
|
|
23
34
|
stream: (text: TextSource, options?: SynthesisOptions) => AudioStream;
|
|
24
35
|
};
|
|
25
36
|
readonly voices: {
|
package/dist/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { STT } from "./stt/client.js";
|
|
1
2
|
import { Credentials } from "./auth.js";
|
|
2
3
|
import { RimeAuthenticationError, RimeInputError } from "./errors.js";
|
|
3
4
|
import { TTS, } from "./tts/client.js";
|
|
@@ -6,6 +7,8 @@ import { Realtime } from "./realtime/client.js";
|
|
|
6
7
|
export class Rime {
|
|
7
8
|
credentials;
|
|
8
9
|
ttsClient;
|
|
10
|
+
sttClient;
|
|
11
|
+
stt;
|
|
9
12
|
realtimeClient;
|
|
10
13
|
realtime;
|
|
11
14
|
closed = false;
|
|
@@ -22,12 +25,17 @@ export class Rime {
|
|
|
22
25
|
const defaultTimeout = timeout(options.timeout);
|
|
23
26
|
const deployment = resolve(options.model ?? "coda", options.endpoint);
|
|
24
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),
|
|
31
|
+
};
|
|
25
32
|
this.realtimeClient = new Realtime(this.credentials, () => this.checkOpen());
|
|
26
33
|
this.realtime = {
|
|
27
34
|
connect: (options) => this.realtimeClient.connect(options),
|
|
28
35
|
};
|
|
29
36
|
this.ttsClient = new TTS(this.credentials, deployment, defaultTimeout, () => this.checkOpen());
|
|
30
37
|
this.tts = {
|
|
38
|
+
synthesize: (text, options) => this.ttsClient.synthesize(text, options),
|
|
31
39
|
stream: (text, options) => this.ttsClient.stream(text, options),
|
|
32
40
|
};
|
|
33
41
|
this.voices = this.ttsClient.voices;
|
|
@@ -41,11 +49,19 @@ export class Rime {
|
|
|
41
49
|
if (!this.closing) {
|
|
42
50
|
this.closed = true;
|
|
43
51
|
this.closing = (async () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
}
|
|
49
65
|
})();
|
|
50
66
|
}
|
|
51
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;
|
package/dist/grpc.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { abortable } from "./cancellation.js";
|
|
2
|
+
import * as grpc from "@grpc/grpc-js";
|
|
3
|
+
import { fromBinary, toBinary, } from "@bufbuild/protobuf";
|
|
4
|
+
import * as errors from "./errors.js";
|
|
5
|
+
export function rpcError(code, requestId = null) {
|
|
6
|
+
const classes = {
|
|
7
|
+
[grpc.status.UNAUTHENTICATED]: errors.RimeAuthenticationError,
|
|
8
|
+
[grpc.status.PERMISSION_DENIED]: errors.RimePermissionError,
|
|
9
|
+
[grpc.status.INVALID_ARGUMENT]: errors.RimeInputError,
|
|
10
|
+
[grpc.status.RESOURCE_EXHAUSTED]: errors.RimeResourceLimitError,
|
|
11
|
+
[grpc.status.UNAVAILABLE]: errors.RimeUnavailableError,
|
|
12
|
+
[grpc.status.DEADLINE_EXCEEDED]: errors.RimeTimeoutError,
|
|
13
|
+
[grpc.status.CANCELLED]: errors.RimeCancelledError,
|
|
14
|
+
};
|
|
15
|
+
const ErrorType = classes[code] ?? errors.RimeStreamError;
|
|
16
|
+
return new ErrorType(`Rime operation failed: ${grpc.status[code] ?? "UNKNOWN"}`, requestId);
|
|
17
|
+
}
|
|
18
|
+
export function serializer(desc) {
|
|
19
|
+
return (message) => Buffer.from(toBinary(desc, message));
|
|
20
|
+
}
|
|
21
|
+
export function deserializer(desc) {
|
|
22
|
+
return (data) => fromBinary(desc, data);
|
|
23
|
+
}
|
|
24
|
+
export function requestId(metadata) {
|
|
25
|
+
return metadata?.get("x-request-id")[0]?.toString() ?? null;
|
|
26
|
+
}
|
|
27
|
+
export function nativeError(error, id) {
|
|
28
|
+
if (error instanceof errors.RimeError)
|
|
29
|
+
return error;
|
|
30
|
+
if (error &&
|
|
31
|
+
typeof error === "object" &&
|
|
32
|
+
"code" in error &&
|
|
33
|
+
typeof error.code === "number") {
|
|
34
|
+
const metadata = "metadata" in error ? error.metadata : null;
|
|
35
|
+
return rpcError(error.code, id ?? (metadata instanceof grpc.Metadata ? requestId(metadata) : null));
|
|
36
|
+
}
|
|
37
|
+
return new errors.RimeStreamError("Rime transport failed", id, {
|
|
38
|
+
cause: error,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export class BidiCall {
|
|
42
|
+
call;
|
|
43
|
+
signal;
|
|
44
|
+
metadataPromise;
|
|
45
|
+
statusPromise;
|
|
46
|
+
status = null;
|
|
47
|
+
id = null;
|
|
48
|
+
constructor(call, signal) {
|
|
49
|
+
this.call = call;
|
|
50
|
+
this.signal = signal;
|
|
51
|
+
this.metadataPromise = new Promise((resolve, reject) => {
|
|
52
|
+
this.call.once("metadata", (metadata) => {
|
|
53
|
+
this.id = requestId(metadata);
|
|
54
|
+
resolve(metadata);
|
|
55
|
+
});
|
|
56
|
+
// A trailers-only response has no metadata event.
|
|
57
|
+
this.call.once("status", () => resolve(new grpc.Metadata()));
|
|
58
|
+
this.call.once("error", reject);
|
|
59
|
+
});
|
|
60
|
+
this.metadataPromise.catch(() => { });
|
|
61
|
+
this.statusPromise = new Promise((resolve) => {
|
|
62
|
+
this.call.once("status", (status) => {
|
|
63
|
+
this.status = status;
|
|
64
|
+
this.id ??= requestId(status.metadata);
|
|
65
|
+
resolve(status);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
this.call.on("error", () => { });
|
|
69
|
+
}
|
|
70
|
+
get requestId() {
|
|
71
|
+
return this.id;
|
|
72
|
+
}
|
|
73
|
+
get done() {
|
|
74
|
+
return this.status !== null || this.call.destroyed;
|
|
75
|
+
}
|
|
76
|
+
cancel() {
|
|
77
|
+
this.call.cancel();
|
|
78
|
+
}
|
|
79
|
+
error(error) {
|
|
80
|
+
const failure = nativeError(error, this.id);
|
|
81
|
+
this.id ??= failure.requestId;
|
|
82
|
+
return failure;
|
|
83
|
+
}
|
|
84
|
+
async write(message) {
|
|
85
|
+
try {
|
|
86
|
+
this.signal.throwIfAborted();
|
|
87
|
+
if (this.status) {
|
|
88
|
+
if (this.status.code !== grpc.status.OK)
|
|
89
|
+
throw rpcError(this.status.code, this.id);
|
|
90
|
+
throw new errors.RimeStreamError("The service completed before input finished", this.id);
|
|
91
|
+
}
|
|
92
|
+
await abortable(new Promise((resolve, reject) => {
|
|
93
|
+
this.call.write(message, (error) => error ? reject(error) : resolve());
|
|
94
|
+
}), this.signal);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (this.signal.aborted)
|
|
98
|
+
throw this.signal.reason;
|
|
99
|
+
if (error instanceof errors.RimeError)
|
|
100
|
+
throw error;
|
|
101
|
+
// A native write error can precede the final status event. Preserve the
|
|
102
|
+
// server rejection even if the reader has not consumed a response.
|
|
103
|
+
const status = await abortable(this.statusPromise, this.signal);
|
|
104
|
+
if (status.code !== grpc.status.OK)
|
|
105
|
+
throw rpcError(status.code, this.id);
|
|
106
|
+
throw this.error(error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
finishInput() {
|
|
110
|
+
try {
|
|
111
|
+
this.call.end();
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw this.error(error);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
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
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async *responses() {
|
|
128
|
+
try {
|
|
129
|
+
await this.headers();
|
|
130
|
+
const responses = this.call;
|
|
131
|
+
for await (const response of responses)
|
|
132
|
+
yield response;
|
|
133
|
+
const status = await abortable(this.statusPromise, this.signal);
|
|
134
|
+
if (status.code !== grpc.status.OK)
|
|
135
|
+
throw rpcError(status.code, this.id);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
if (this.signal.aborted)
|
|
139
|
+
throw this.signal.reason;
|
|
140
|
+
throw this.error(error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
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
|
@@ -4,3 +4,5 @@ export { AudioFormat } from "./tts/audio.js";
|
|
|
4
4
|
export * from "./errors.js";
|
|
5
5
|
export { RealtimeSession } from "./realtime/session.js";
|
|
6
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
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
|
+
}
|
package/dist/realtime/async.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { scope } from "../cancellation.js";
|
|
1
2
|
export declare class Deferred<T> {
|
|
2
3
|
readonly promise: Promise<T>;
|
|
3
4
|
resolve: (value: T) => void;
|
|
@@ -16,11 +17,6 @@ export declare class Mutex {
|
|
|
16
17
|
private waiters;
|
|
17
18
|
acquire(signal: AbortSignal): Promise<() => void>;
|
|
18
19
|
}
|
|
19
|
-
/** A disposable deadline avoids retaining timers and caller abort listeners. */
|
|
20
|
-
export declare function scope(signals: (AbortSignal | undefined)[], seconds?: number, error?: Error): {
|
|
21
|
-
signal: AbortSignal;
|
|
22
|
-
dispose(): void;
|
|
23
|
-
};
|
|
24
20
|
export declare function cancellation(signal?: AbortSignal): {
|
|
25
21
|
signal: AbortSignal;
|
|
26
22
|
dispose: () => void | undefined;
|
package/dist/realtime/async.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { scope } from "../cancellation.js";
|
|
1
2
|
import { abortable } from "../cancellation.js";
|
|
2
3
|
import { RimeCancelledError } from "../errors.js";
|
|
3
4
|
export class Deferred {
|
|
@@ -72,42 +73,6 @@ export class Mutex {
|
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
|
-
/** A disposable deadline avoids retaining timers and caller abort listeners. */
|
|
76
|
-
export function scope(signals, seconds, error) {
|
|
77
|
-
const controller = new AbortController();
|
|
78
|
-
const handlers = new Map();
|
|
79
|
-
for (const signal of new Set(signals)) {
|
|
80
|
-
if (!signal)
|
|
81
|
-
continue;
|
|
82
|
-
const abort = () => controller.abort(signal.reason);
|
|
83
|
-
if (signal.aborted)
|
|
84
|
-
abort();
|
|
85
|
-
else {
|
|
86
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
87
|
-
handlers.set(signal, abort);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
let timer;
|
|
91
|
-
if (seconds !== undefined) {
|
|
92
|
-
const end = performance.now() + seconds * 1000;
|
|
93
|
-
const expire = () => {
|
|
94
|
-
const remaining = end - performance.now();
|
|
95
|
-
if (remaining <= 0)
|
|
96
|
-
controller.abort(error);
|
|
97
|
-
else
|
|
98
|
-
timer = setTimeout(expire, Math.min(remaining, 2147483647));
|
|
99
|
-
};
|
|
100
|
-
expire();
|
|
101
|
-
}
|
|
102
|
-
return {
|
|
103
|
-
signal: controller.signal,
|
|
104
|
-
dispose() {
|
|
105
|
-
clearTimeout(timer);
|
|
106
|
-
for (const [signal, handler] of handlers)
|
|
107
|
-
signal.removeEventListener("abort", handler);
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
76
|
export function cancellation(signal) {
|
|
112
77
|
const controller = new AbortController();
|
|
113
78
|
const abort = () => controller.abort(new RimeCancelledError("Realtime operation cancelled"));
|
package/dist/realtime/audio.d.ts
CHANGED
|
@@ -1,13 +1,2 @@
|
|
|
1
1
|
import type { AudioChunk, PCMFormat } from "./types.js";
|
|
2
2
|
export declare function formatOf(chunk: AudioChunk): Required<PCMFormat>;
|
|
3
|
-
/** Streaming linear interpolation, matching Python audioop.ratecv with no filter.
|
|
4
|
-
* Integer phase remains bounded and preserves samples across chunk boundaries.
|
|
5
|
-
*/
|
|
6
|
-
export declare class InputConverter {
|
|
7
|
-
readonly format: Required<PCMFormat>;
|
|
8
|
-
private phase;
|
|
9
|
-
private previous;
|
|
10
|
-
constructor(format: Required<PCMFormat>);
|
|
11
|
-
clone(): InputConverter;
|
|
12
|
-
process(input: Uint8Array): Buffer;
|
|
13
|
-
}
|
package/dist/realtime/audio.js
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { RimeAudioFormatError } from "../errors.js";
|
|
2
|
+
import { resolvePCMFormat } from "../pcm.js";
|
|
2
3
|
export function formatOf(chunk) {
|
|
3
|
-
const format =
|
|
4
|
-
sampleRate: chunk.format?.sampleRate ?? 16000,
|
|
5
|
-
channels: chunk.format?.channels ?? 1,
|
|
6
|
-
encoding: chunk.format?.encoding ?? "pcm_s16le",
|
|
7
|
-
};
|
|
8
|
-
if (![8000, 16000, 24000, 48000].includes(format.sampleRate) ||
|
|
9
|
-
![1, 2].includes(format.channels) ||
|
|
10
|
-
format.encoding !== "pcm_s16le")
|
|
11
|
-
throw new RimeAudioFormatError("Use PCM16 at 8, 16, 24 or 48 kHz, with one or two channels");
|
|
4
|
+
const format = resolvePCMFormat(chunk.format);
|
|
12
5
|
if (!(chunk.data instanceof Uint8Array) ||
|
|
13
6
|
chunk.data.length % (2 * format.channels))
|
|
14
7
|
throw new RimeAudioFormatError("Audio must contain complete PCM16 frames as Uint8Array");
|
|
@@ -16,39 +9,3 @@ export function formatOf(chunk) {
|
|
|
16
9
|
throw new RimeAudioFormatError("Send audio in chunks of at most 192000 bytes");
|
|
17
10
|
return format;
|
|
18
11
|
}
|
|
19
|
-
/** Streaming linear interpolation, matching Python audioop.ratecv with no filter.
|
|
20
|
-
* Integer phase remains bounded and preserves samples across chunk boundaries.
|
|
21
|
-
*/
|
|
22
|
-
export class InputConverter {
|
|
23
|
-
format;
|
|
24
|
-
phase = -16000;
|
|
25
|
-
previous = 0;
|
|
26
|
-
constructor(format) {
|
|
27
|
-
this.format = format;
|
|
28
|
-
}
|
|
29
|
-
clone() {
|
|
30
|
-
const copy = new InputConverter(this.format);
|
|
31
|
-
copy.phase = this.phase;
|
|
32
|
-
copy.previous = this.previous;
|
|
33
|
-
return copy;
|
|
34
|
-
}
|
|
35
|
-
process(input) {
|
|
36
|
-
const data = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
37
|
-
const samples = [];
|
|
38
|
-
for (let offset = 0; offset < data.length; offset += this.format.channels * 2) {
|
|
39
|
-
const current = this.format.channels === 1
|
|
40
|
-
? data.readInt16LE(offset)
|
|
41
|
-
: Math.floor((data.readInt16LE(offset) + data.readInt16LE(offset + 2)) / 2);
|
|
42
|
-
this.phase += 16000;
|
|
43
|
-
while (this.phase >= 0) {
|
|
44
|
-
samples.push(Math.floor((this.previous * this.phase + current * (16000 - this.phase)) /
|
|
45
|
-
16000));
|
|
46
|
-
this.phase -= this.format.sampleRate;
|
|
47
|
-
}
|
|
48
|
-
this.previous = current;
|
|
49
|
-
}
|
|
50
|
-
const output = Buffer.alloc(samples.length * 2);
|
|
51
|
-
samples.forEach((sample, index) => output.writeInt16LE(sample, index * 2));
|
|
52
|
-
return output;
|
|
53
|
-
}
|
|
54
|
-
}
|