@rimelabs/sdk 0.1.0-alpha.12 → 0.1.0-alpha.14

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.0-alpha.14](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.13...typescript-v0.1.0-alpha.14) (2026-10-12)
4
+
5
+
6
+ ### Features
7
+
8
+ * **tts:** add sample rates, time-scale control, and buffered audio output ([1856d0d](https://github.com/rimelabs/rime-sdk/commit/1856d0d6c00a46d7917b61d73bc7f2a8526c8bb0))
9
+
10
+ ## [0.1.0-alpha.13](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.12...typescript-v0.1.0-alpha.13) (2026-10-11)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **tts:** remove complete-text synthesis option ([#61](https://github.com/rimelabs/rime-sdk/issues/61)) ([4e3c133](https://github.com/rimelabs/rime-sdk/commit/4e3c1338dc9b129a0201a462f12fb414eeb520d1))
16
+
3
17
  ## [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
18
 
5
19
 
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
- # Rime SDK for Node.js
1
+ # Rime SDK for TypeScript and Node.js
2
2
 
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
3
+ Generate speech with Coda or Mist v3, transcribe audio with STT, and build spoken
4
+ conversations with Prism through the `Rime` client. Your application handles
5
5
  audio capture and playback.
6
6
 
7
- ## Install
7
+ ## Installation
8
8
 
9
- Requires Node.js 22 or later. This alpha package uses ESM and includes TypeScript types.
10
- Run it on a server. Browser use is not supported.
9
+ Requires **Node.js 22 or later**. Install the alpha package from npm. It uses
10
+ ESM, includes TypeScript types, and runs on a server; browsers are not supported.
11
11
 
12
12
  ```sh
13
13
  mkdir rime-quickstart
@@ -17,82 +17,14 @@ npm install @rimelabs/sdk wavefile
17
17
  export RIME_API_KEY="your-api-key"
18
18
  ```
19
19
 
20
- Get a TTS API key from the [Rime dashboard](https://app.rime.ai/).
21
- An explicit `new Rime({ apiKey: "..." })` overrides `RIME_API_KEY`. The SDK does not load `.env` files.
22
- The commands below use a macOS or Linux shell.
20
+ For an existing project, skip the first three commands. Get an API key from the
21
+ [Rime dashboard](https://app.rime.ai/). A key is required: `new Rime()` reads
22
+ `RIME_API_KEY`, or you can pass `new Rime({ apiKey: "..." })` to override it.
23
+ The SDK does not load `.env` files. Shell commands below assume macOS or Linux.
23
24
 
24
- These examples use JavaScript `.mjs` files so you can run them without a TypeScript build.
25
- The additional [wavefile package](https://github.com/rochars/wavefile) reads and writes WAV files
26
- for the examples. It is not required by the SDK.
27
-
28
- ## TTS: save speech
29
-
30
- Save this as `tts.mjs`. It writes a playable `speech.wav` file with no audio-device dependencies.
31
-
32
- ```javascript
33
- import { writeFileSync } from "node:fs";
34
- import wavefile from "wavefile";
35
- import { Rime } from "@rimelabs/sdk";
36
-
37
- const client = new Rime({ timeout: 60 });
38
- try {
39
- const chunks = [];
40
- for await (const chunk of client.tts.synthesize(
41
- "Your appointment is confirmed for tomorrow.",
42
- )) {
43
- chunks.push(Buffer.from(chunk));
44
- }
45
- const pcm = Buffer.concat(chunks);
46
- const samples = Array.from({ length: pcm.length / 2 }, (_, i) =>
47
- pcm.readInt16LE(i * 2),
48
- );
49
- const wav = new wavefile.WaveFile();
50
- wav.fromScratch(1, 24000, "16", samples);
51
- writeFileSync("speech.wav", wav.toBuffer());
52
- console.log("Saved speech.wav");
53
- } finally {
54
- await client.close();
55
- }
56
- ```
57
-
58
- Run `node tts.mjs`, then open `speech.wav` in your audio player.
59
- This short example collects the audio in memory before writing the file.
60
- For long responses, write audio chunks as they arrive.
61
-
62
- Coda is the default model. Use `new Rime({ model: "mistv3" })` for Mist v3, or pass
63
- `{ voice: "your-voice" }` as the second argument to `client.tts.synthesize(...)` to select a voice.
64
- The default output is mono 24 kHz PCM16. The example adds its WAV header.
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.
25
+ Examples use JavaScript `.mjs` files so you can run them without a TypeScript build.
26
+ The additional `wavefile` dependency is only needed by the Prism example; TTS and
27
+ STT use the SDK and Node.js built-ins.
96
28
 
97
29
  ## Prism: send recorded speech
98
30
 
@@ -195,11 +127,176 @@ stop playback on interruption, and report the actual played position to Prism.
195
127
  Keep the API key on your server when you add a browser interface.
196
128
  TTS settings do not configure Prism sessions.
197
129
 
130
+ ## TTS
131
+
132
+ Use `client.tts.synthesize(text, options)` for a complete string, or
133
+ `client.tts.stream(textChunks, options)` for an async iterable of strings.
134
+ Both return an `AudioStream` immediately; consume it with `for await`, without
135
+ awaiting the method call. For complete audio in memory, await
136
+ `client.tts.synthesizeToBuffer(text, options)` to receive a Node.js `Buffer`.
137
+ Text is required and must contain non-whitespace characters.
138
+
139
+ ### Client options
140
+
141
+ These optional `new Rime({...})` fields configure TTS. They do not configure STT or Prism.
142
+
143
+ | Option | Purpose and values | Required | Default |
144
+ | --- | --- | --- | --- |
145
+ | `model` | Select `"coda"` or `"mistv3"`. | No | `"coda"` |
146
+ | `endpoint` | Override the TTS TLS hostname and optional port, such as `"coda.api.rime.ai:443"`; no scheme or path. | No | `coda.api.rime.ai:443` for Coda; `mist.api.rime.ai:443` for Mist v3. |
147
+ | `timeout` | Overall TTS timeout in positive seconds, such as `60`; `null` disables it. | No | No overall timeout. |
148
+
149
+ ### Synthesis options
150
+
151
+ All three TTS methods accept the same `SynthesisOptions` fields. Import
152
+ `AudioFormat` from `@rimelabs/sdk` when selecting an encoding.
153
+
154
+ | Option | Purpose and values | Required | Default |
155
+ | --- | --- | --- | --- |
156
+ | `voice` | Voice name for the selected model, such as `"clementine"` or `"astra"`. | No | `"clementine"` for Coda; `"astra"` for Mist v3. |
157
+ | `language` | Spoken language code, such as `"en"` or `"es"`; the service validates support. | No | `"en"` |
158
+ | `audioFormat` | Output encoding: `AudioFormat.PCM`, `.WAV`, `.MP3`, `.PCM_24000`, or `.MULAW_8000`. | No | Raw PCM16 at 24 kHz (`PCM_24000`). |
159
+ | `sampleRate` | Requested output rate in Hz: `8000`, `16000`, or `24000`. | No | `24000`; `8000` for `MULAW_8000`. |
160
+ | `timeScaleFactor` | Duration multiplier from `0.4` to `2.5`; below `1` speeds speech up, above `1` slows it down. | No | Omitted; normal duration (`1`). |
161
+ | `timestamps` | `true` requests word timings; supported only with Mist v3. | No | `false` |
162
+ | `customLexicon` | Array of `{ spelling, pronunciation }` entries for Coda; pronunciations use space-separated X-SAMPA strings. | No | Empty array. |
163
+ | `timeout` | Override the overall timeout with positive seconds, or `null` to disable it. | No | Inherits the client timeout. |
164
+
165
+ Discover valid values with `await client.voices.list({ language: "en" })` and
166
+ `await client.languages.list()`. Omit the voice language filter to list all voices
167
+ for the selected model. Both calls accept an options object with `timeout` in
168
+ seconds; discovery has an additional ten-second maximum.
169
+
170
+ ### Example: save an IVR greeting
171
+
172
+ Save as `tts.mjs`. This writes a complete 16 kHz WAV file, ready for playback or upload.
173
+
174
+ ```javascript
175
+ import { writeFile } from "node:fs/promises";
176
+ import { AudioFormat, Rime } from "@rimelabs/sdk";
177
+
178
+ const client = new Rime({ timeout: 60 });
179
+ try {
180
+ const audio = await client.tts.synthesizeToBuffer(
181
+ "Thank you for calling. Your appointment is confirmed for tomorrow.",
182
+ {
183
+ audioFormat: AudioFormat.WAV,
184
+ sampleRate: 16000,
185
+ timeScaleFactor: 0.9,
186
+ },
187
+ );
188
+ await writeFile("greeting.wav", audio);
189
+ console.log("Saved greeting.wav");
190
+ } finally {
191
+ await client.close();
192
+ }
193
+ ```
194
+
195
+ Run `node tts.mjs`, then open `greeting.wav` in an audio player.
196
+
197
+ ### Audio and stream behavior
198
+
199
+ All output is mono. PCM is signed 16-bit little-endian audio without a header;
200
+ WAV contains PCM16; MP3 is encoded by the service. PCM/WAV rates are requested
201
+ from the server. MP3 headers determine the actual encoded rate. The fixed
202
+ `PCM_24000` and `MULAW_8000` presets reject conflicting sample rates; mu-law is
203
+ converted locally from 24 kHz PCM. Read `audio.sampleRate` on an `AudioStream`
204
+ for the requested rate.
205
+
206
+ The buffer helper waits for successful completion and finalizes WAV lengths.
207
+ Its fixed limit is **64 MiB per call**, including headers; exceeding it throws
208
+ `RimeResourceLimitError` without returning partial audio. WAV/MP3 stream chunks
209
+ are fragments of one file. Use the buffer helper when you need a complete WAV.
210
+
211
+ For incremental text, send fragments as they arrive; the service combines them
212
+ into sentences. Consume streaming audio to the end to confirm success, since an
213
+ error can follow earlier audio. Use `await audio.cancel()` when stopping early
214
+ and `await client.close()` in a `finally` block to release the client. Synthesis
215
+ is not retried automatically, and overall timeouts include pauses by the caller;
216
+ connection and progress limits also apply.
217
+
218
+ For timestamps, enable `timestamps: true`, consume the audio stream fully, then
219
+ call `await audio.timestamps()`. The result has `status` and `spans` with word
220
+ text and start/end seconds. Nonzero alignment status does not fail the audio.
221
+ The buffer helper returns only bytes, so use an audio stream to access timings.
222
+ Custom lexicons apply to the whole request; the service validates pronunciations
223
+ and model/language support.
224
+
225
+ ## STT
226
+
227
+ Use `client.stt.stream(audioChunks, options)` to transcribe one utterance.
228
+ The required source is an async iterable of headerless signed PCM16 little-endian
229
+ `Uint8Array` chunks (`Buffer` also works). Ending the source ends the utterance;
230
+ your application decides when to stop.
231
+
232
+ STT uses streaming gRPC over TLS to `stt.api.rime.ai:443`, with the client's API
233
+ key. There is no STT model, endpoint, or protocol selector. TTS client settings,
234
+ including its timeout, do not apply to STT.
235
+
236
+ ### Transcription options
237
+
238
+ Pass these fields in the required `TranscriptionOptions` object.
239
+
240
+ | Option | Purpose and values | Required | Default |
241
+ | --- | --- | --- | --- |
242
+ | `language` | Spoken BCP-47 language tag, such as `"en"` or `"es"`; passed to the service. | Yes | None; no automatic detection. |
243
+ | `mode` | `"written"` normalizes written text; `"verbatim"` prefers spoken wording. | No | `"written"` |
244
+ | `contextTerms` | Array of recognition hints, such as `["Rime"]`; hints do not force a match. | No | Empty array. |
245
+ | `inputFormat` | Object with `sampleRate` (`8000`, `16000`, `24000`, `48000`), `channels` (`1` or `2`), and `encoding` (only `"pcm_s16le"`). | No | 16 kHz mono PCM16; each field is optional. |
246
+ | `timeout` | Overall timeout in positive seconds, including source/consumer pauses; `null` disables it. | No | No overall timeout; independent of the client timeout. |
247
+ | `signal` | An `AbortSignal`, such as `controller.signal`, to cancel this utterance. | No | No signal. |
248
+
249
+ ### Example: transcribe a recording
250
+
251
+ Save a short recording as `utterance.pcm`: **16 kHz, mono, signed PCM16
252
+ little-endian, with no WAV header**. Save this as `stt.mjs`.
253
+
254
+ ```javascript
255
+ import { createReadStream } from "node:fs";
256
+ import { Rime } from "@rimelabs/sdk";
257
+
258
+ async function* audioChunks() {
259
+ yield* createReadStream("utterance.pcm");
260
+ }
261
+
262
+ const client = new Rime();
263
+ try {
264
+ const transcript = client.stt.stream(audioChunks(), {
265
+ language: "en",
266
+ timeout: 120,
267
+ });
268
+ for await (const update of transcript) {
269
+ console.log(update.kind, update.text);
270
+ }
271
+ } finally {
272
+ await client.close();
273
+ }
274
+ ```
275
+
276
+ Run `node stt.mjs`. Each partial update replaces the **complete current
277
+ transcript**; do not append snapshots. A final update confirms successful
278
+ completion and may be empty for silence. Errors do not turn partials into finals.
279
+
280
+ The SDK converts supported input formats to 16 kHz mono. Stereo must be
281
+ interleaved. WAV headers, MP3, and mu-law input are not accepted. Chunks may split
282
+ sample frames, but an incomplete final frame fails the request. The SDK does not
283
+ provide microphone capture, automatic end-of-speech detection, word timings, or
284
+ diarization. Recognition is not retried automatically.
285
+
286
+ Use `await transcript.cancel()` or the supplied `AbortSignal` to stop one
287
+ utterance. Closing the client cancels all its operations. Audio sources must
288
+ yield without blocking the event loop. Connection, acceptance, and completion
289
+ limits still apply when the overall timeout is disabled.
290
+
198
291
  ## Help and licenses
199
292
 
200
- See [Rime documentation](https://docs.rime.ai/) for API keys, TTS models, and deployment information.
201
- The examples above run with the installed package. No source checkout is required.
293
+ See the [TTS guide](https://github.com/rimelabs/rime-sdk/blob/main/typescript/docs/tts.md),
294
+ [STT guide](https://github.com/rimelabs/rime-sdk/blob/main/typescript/docs/stt.md), and
295
+ [Prism guide](https://github.com/rimelabs/rime-sdk/blob/main/typescript/docs/realtime.md)
296
+ for more examples. Visit [Rime documentation](https://docs.rime.ai/) for service
297
+ information, or report SDK issues on [GitHub](https://github.com/rimelabs/rime-sdk/issues).
298
+ Include the SDK version and the error's `requestId` when available; omit API keys.
202
299
 
203
- SDK code uses the MIT license. The Prism protocol module uses Apache 2.0.
204
- BlingFire retains its MIT license. License texts and notices are included in the package.
205
- Its WASM runtime ships with the package. The SDK downloads no code or models at runtime.
300
+ SDK code uses the [MIT license](LICENSE). The Prism protocol module uses
301
+ [Apache 2.0](LICENSE-PRISM); see its [notices](NOTICE-PRISM).
302
+ License texts and notices are included in the distribution.
package/dist/client.d.ts CHANGED
@@ -11,8 +11,6 @@ export interface RimeOptions {
11
11
  model?: string;
12
12
  endpoint?: string | null;
13
13
  timeout?: number | null;
14
- /** Recognition hostname and optional port; defaults to stt.api.rime.ai:443. */
15
- sttEndpoint?: string | null;
16
14
  }
17
15
  export declare class Rime {
18
16
  private readonly credentials;
@@ -30,6 +28,8 @@ export declare class Rime {
30
28
  readonly tts: {
31
29
  /** Synthesize complete text and return streaming audio. Do not await this call. */
32
30
  synthesize: (text: string, options?: SynthesisOptions) => AudioStream;
31
+ /** Await complete audio bytes, finalizing WAV lengths. Fixed output limit: 64 MiB. */
32
+ synthesizeToBuffer: (text: string, options?: SynthesisOptions) => Promise<Buffer>;
33
33
  /** Supply incremental text and return streaming audio. Do not await this call. */
34
34
  stream: (text: TextSource, options?: SynthesisOptions) => AudioStream;
35
35
  };
package/dist/client.js CHANGED
@@ -25,7 +25,7 @@ export class Rime {
25
25
  const defaultTimeout = timeout(options.timeout);
26
26
  const deployment = resolve(options.model ?? "coda", options.endpoint);
27
27
  this.credentials = new Credentials(key, deployment);
28
- this.sttClient = new STT(this.credentials, options.sttEndpoint, () => this.checkOpen());
28
+ this.sttClient = new STT(this.credentials, () => this.checkOpen());
29
29
  this.stt = {
30
30
  stream: (audio, options) => this.sttClient.stream(audio, options),
31
31
  };
@@ -36,6 +36,7 @@ export class Rime {
36
36
  this.ttsClient = new TTS(this.credentials, deployment, defaultTimeout, () => this.checkOpen());
37
37
  this.tts = {
38
38
  synthesize: (text, options) => this.ttsClient.synthesize(text, options),
39
+ synthesizeToBuffer: (text, options) => this.ttsClient.synthesizeToBuffer(text, options),
39
40
  stream: (text, options) => this.ttsClient.stream(text, options),
40
41
  };
41
42
  this.voices = this.ttsClient.voices;
package/dist/grpc.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as grpc from "@grpc/grpc-js";
2
2
  import { type DescMessage, type MessageShape } from "@bufbuild/protobuf";
3
3
  import * as errors from "./errors.js";
4
- export declare function rpcError(code: number, requestId?: string | null): errors.RimeError;
4
+ export declare function rpcError(code: number, requestId?: string | null, details?: string): errors.RimeError;
5
5
  export declare function serializer<D extends DescMessage>(desc: D): (message: MessageShape<D>) => Buffer<ArrayBuffer>;
6
6
  export declare function deserializer<D extends DescMessage>(desc: D): (data: Buffer) => MessageShape<D>;
7
7
  export declare function requestId(metadata?: grpc.Metadata): string | null;
@@ -18,10 +18,10 @@ export declare class BidiCall<Input, Output> {
18
18
  get done(): boolean;
19
19
  cancel(): void;
20
20
  private error;
21
- write(message: Input): Promise<void>;
22
- finishInput(): void;
23
21
  headers(): Promise<grpc.Metadata>;
24
22
  responses(): AsyncGenerator<Output>;
23
+ write(message: Input): Promise<void>;
24
+ finishInput(): void;
25
25
  }
26
26
  export declare function connect(client: grpc.Client, signal: AbortSignal, seconds: number): Promise<void>;
27
27
  export declare function metadata(value: string): grpc.Metadata;
package/dist/grpc.js CHANGED
@@ -2,7 +2,7 @@ import { abortable } from "./cancellation.js";
2
2
  import * as grpc from "@grpc/grpc-js";
3
3
  import { fromBinary, toBinary, } from "@bufbuild/protobuf";
4
4
  import * as errors from "./errors.js";
5
- export function rpcError(code, requestId = null) {
5
+ export function rpcError(code, requestId = null, details) {
6
6
  const classes = {
7
7
  [grpc.status.UNAUTHENTICATED]: errors.RimeAuthenticationError,
8
8
  [grpc.status.PERMISSION_DENIED]: errors.RimePermissionError,
@@ -13,7 +13,9 @@ export function rpcError(code, requestId = null) {
13
13
  [grpc.status.CANCELLED]: errors.RimeCancelledError,
14
14
  };
15
15
  const ErrorType = classes[code] ?? errors.RimeStreamError;
16
- return new ErrorType(`Rime operation failed: ${grpc.status[code] ?? "UNKNOWN"}`, requestId);
16
+ return new ErrorType(details?.trim()
17
+ ? details
18
+ : `Rime operation failed: ${grpc.status[code] ?? "UNKNOWN"}`, requestId);
17
19
  }
18
20
  export function serializer(desc) {
19
21
  return (message) => Buffer.from(toBinary(desc, message));
@@ -32,7 +34,9 @@ export function nativeError(error, id) {
32
34
  "code" in error &&
33
35
  typeof error.code === "number") {
34
36
  const metadata = "metadata" in error ? error.metadata : null;
35
- return rpcError(error.code, id ?? (metadata instanceof grpc.Metadata ? requestId(metadata) : null));
37
+ return rpcError(error.code, id ?? (metadata instanceof grpc.Metadata ? requestId(metadata) : null), "details" in error && typeof error.details === "string"
38
+ ? error.details
39
+ : undefined);
36
40
  }
37
41
  return new errors.RimeStreamError("Rime transport failed", id, {
38
42
  cause: error,
@@ -81,16 +85,60 @@ export class BidiCall {
81
85
  this.id ??= failure.requestId;
82
86
  return failure;
83
87
  }
88
+ async headers() {
89
+ try {
90
+ return await abortable(this.metadataPromise, this.signal);
91
+ }
92
+ catch (error) {
93
+ if (this.signal.aborted)
94
+ throw this.signal.reason;
95
+ throw this.error(error);
96
+ }
97
+ }
98
+ async *responses() {
99
+ try {
100
+ await this.headers();
101
+ const responses = this.call;
102
+ for await (const response of responses)
103
+ yield response;
104
+ const status = await abortable(this.statusPromise, this.signal);
105
+ if (status.code !== grpc.status.OK)
106
+ throw rpcError(status.code, this.id, status.details);
107
+ }
108
+ catch (error) {
109
+ if (this.signal.aborted)
110
+ throw this.signal.reason;
111
+ throw this.error(error);
112
+ }
113
+ }
84
114
  async write(message) {
85
115
  try {
86
116
  this.signal.throwIfAborted();
87
117
  if (this.status) {
88
118
  if (this.status.code !== grpc.status.OK)
89
- throw rpcError(this.status.code, this.id);
119
+ throw rpcError(this.status.code, this.id, this.status.details);
90
120
  throw new errors.RimeStreamError("The service completed before input finished", this.id);
91
121
  }
92
122
  await abortable(new Promise((resolve, reject) => {
93
- this.call.write(message, (error) => error ? reject(error) : resolve());
123
+ const finish = (error) => {
124
+ this.call.removeListener("status", onStatus);
125
+ if (error)
126
+ reject(error);
127
+ else
128
+ resolve();
129
+ };
130
+ // A rejected native write can emit final status without invoking its
131
+ // callback (for example, when exceeding the send-message limit).
132
+ const onStatus = (status) => finish(status.code === grpc.status.OK
133
+ ? new errors.RimeStreamError("The service completed before input finished", this.id)
134
+ : rpcError(status.code, this.id, status.details));
135
+ this.call.once("status", onStatus);
136
+ try {
137
+ this.call.write(message, finish);
138
+ }
139
+ catch (error) {
140
+ finish(error);
141
+ }
94
142
  }), this.signal);
95
143
  }
96
144
  catch (error) {
@@ -102,7 +150,7 @@ export class BidiCall {
102
150
  // server rejection even if the reader has not consumed a response.
103
151
  const status = await abortable(this.statusPromise, this.signal);
104
152
  if (status.code !== grpc.status.OK)
105
- throw rpcError(status.code, this.id);
153
+ throw rpcError(status.code, this.id, status.details);
106
154
  throw this.error(error);
107
155
  }
108
156
  }
@@ -114,32 +162,6 @@ export class BidiCall {
114
162
  throw this.error(error);
115
163
  }
116
164
  }
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
165
  }
144
166
  export async function connect(client, signal, seconds) {
145
167
  await abortable(new Promise((resolve, reject) => client.waitForReady(Date.now() + seconds * 1000, (error) => error
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { Rime, type RimeOptions, type SynthesisOptions, type DiscoveryOptions, type VoiceListOptions, } from "./client.js";
2
2
  export { AudioStream } from "./tts/stream.js";
3
3
  export { AudioFormat } from "./tts/audio.js";
4
+ export type { PronunciationEntry } from "./tts/lexicon.js";
5
+ export type { TimestampResult, TimestampStatus, WordTimestamp, } from "./tts/timestamps.js";
4
6
  export * from "./errors.js";
5
7
  export { RealtimeSession } from "./realtime/session.js";
6
8
  export * from "./realtime/types.js";
@@ -3,11 +3,10 @@ import { TranscriptStream } from "./stream.js";
3
3
  import type { AudioSource, TranscriptionOptions } from "./types.js";
4
4
  export declare class STT {
5
5
  private readonly credentials;
6
- private readonly endpoint;
7
6
  private readonly checkOpen;
8
7
  private connection;
9
8
  private streams;
10
- constructor(credentials: Credentials, endpoint: string | null | undefined, checkOpen: () => void);
9
+ constructor(credentials: Credentials, checkOpen: () => void);
11
10
  stream(source: AudioSource, options: TranscriptionOptions): TranscriptStream;
12
11
  close(): Promise<void>;
13
12
  }
@@ -1,5 +1,5 @@
1
1
  import { connect, metadata } from "../grpc.js";
2
- import { endpointAddress, timeout } from "../validation.js";
2
+ import { timeout } from "../validation.js";
3
3
  import { RimeInputError } from "../errors.js";
4
4
  import { resolvePCMFormat } from "../pcm.js";
5
5
  import { transport, requireSchema } from "./transport.js";
@@ -7,13 +7,11 @@ import { TranscriptStream } from "./stream.js";
7
7
  import { policy } from "./policy.js";
8
8
  export class STT {
9
9
  credentials;
10
- endpoint;
11
10
  checkOpen;
12
11
  connection = null;
13
12
  streams = new Set();
14
- constructor(credentials, endpoint, checkOpen) {
13
+ constructor(credentials, checkOpen) {
15
14
  this.credentials = credentials;
16
- this.endpoint = endpoint;
17
15
  this.checkOpen = checkOpen;
18
16
  }
19
17
  stream(source, options) {
@@ -29,9 +27,7 @@ export class STT {
29
27
  if (!Array.isArray(terms) || terms.some((term) => typeof term !== "string"))
30
28
  throw new RimeInputError("contextTerms must be an array of strings");
31
29
  const format = resolvePCMFormat(options.inputFormat);
32
- const target = this.endpoint == null
33
- ? policy.target
34
- : endpointAddress(this.endpoint).target;
30
+ const target = policy.target;
35
31
  const stream = new TranscriptStream({
36
32
  checkOpen: this.checkOpen,
37
33
  forget: (stream) => this.streams.delete(stream),
@@ -1,11 +1,20 @@
1
1
  export declare class AudioFormat {
2
2
  readonly encoding: string;
3
+ /** Fixed preset rate, or zero when selected per request. See AudioStream.sampleRate. */
3
4
  readonly sampleRate: number;
4
5
  readonly channels: number;
6
+ static readonly PCM: Readonly<AudioFormat>;
7
+ static readonly WAV: Readonly<AudioFormat>;
8
+ static readonly MP3: Readonly<AudioFormat>;
5
9
  static readonly PCM_24000: Readonly<AudioFormat>;
6
10
  static readonly MULAW_8000: Readonly<AudioFormat>;
7
11
  private constructor();
8
12
  }
13
+ export declare function wireFormat(format: AudioFormat): string;
14
+ export declare function resolveAudio(format: AudioFormat | null | undefined, sampleRate: number | null | undefined, timeScaleFactor: number | null | undefined): {
15
+ format: AudioFormat;
16
+ sampleRate: number;
17
+ };
9
18
  export declare class Converter {
10
19
  private profile;
11
20
  private tail;
package/dist/tts/audio.js CHANGED
@@ -1,16 +1,52 @@
1
- import { RimeAudioFormatError } from "../errors.js";
1
+ import { RimeAudioFormatError, RimeInputError } from "../errors.js";
2
2
  export class AudioFormat {
3
3
  encoding;
4
4
  sampleRate;
5
5
  channels;
6
+ static PCM = Object.freeze(new AudioFormat("pcm_s16le", 0, 1));
7
+ static WAV = Object.freeze(new AudioFormat("wav", 0, 1));
8
+ static MP3 = Object.freeze(new AudioFormat("mp3", 0, 1));
6
9
  static PCM_24000 = Object.freeze(new AudioFormat("pcm_s16le", 24000, 1));
7
10
  static MULAW_8000 = Object.freeze(new AudioFormat("mulaw", 8000, 1));
8
- constructor(encoding, sampleRate, channels) {
11
+ constructor(encoding,
12
+ /** Fixed preset rate, or zero when selected per request. See AudioStream.sampleRate. */
13
+ sampleRate, channels) {
9
14
  this.encoding = encoding;
10
15
  this.sampleRate = sampleRate;
11
16
  this.channels = channels;
12
17
  }
13
18
  }
19
+ export function wireFormat(format) {
20
+ if (format === AudioFormat.WAV)
21
+ return "audio/wav";
22
+ if (format === AudioFormat.MP3)
23
+ return "audio/mpeg";
24
+ return "audio/pcm";
25
+ }
26
+ export function resolveAudio(format, sampleRate, timeScaleFactor) {
27
+ format ??= AudioFormat.PCM;
28
+ if (![
29
+ AudioFormat.PCM,
30
+ AudioFormat.PCM_24000,
31
+ AudioFormat.MULAW_8000,
32
+ AudioFormat.WAV,
33
+ AudioFormat.MP3,
34
+ ].includes(format))
35
+ throw new RimeAudioFormatError("Select a named AudioFormat");
36
+ sampleRate ??= format.sampleRate || 24000;
37
+ if (![8000, 16000, 24000].includes(sampleRate))
38
+ throw new RimeAudioFormatError("sampleRate must be 8000, 16000, or 24000");
39
+ if (format.sampleRate && sampleRate !== format.sampleRate)
40
+ throw new RimeAudioFormatError("sampleRate conflicts with the fixed AudioFormat preset");
41
+ if (timeScaleFactor != null &&
42
+ (!Number.isFinite(timeScaleFactor) ||
43
+ timeScaleFactor < 0.4 ||
44
+ timeScaleFactor > 2.5))
45
+ throw new RimeInputError("timeScaleFactor must be a finite number from 0.4 to 2.5");
46
+ if (format === AudioFormat.PCM && sampleRate === 24000)
47
+ format = AudioFormat.PCM_24000;
48
+ return { format, sampleRate };
49
+ }
14
50
  const raw = Array.from({ length: 63 }, (_, i) => (i === 31
15
51
  ? (2 * 3400) / 24000
16
52
  : Math.sin(((2 * Math.PI * 3400) / 24000) * (i - 31)) /
@@ -47,12 +83,15 @@ export class Converter {
47
83
  return null;
48
84
  }
49
85
  process(input, final = false) {
86
+ if (this.profile === AudioFormat.WAV || this.profile === AudioFormat.MP3)
87
+ return Buffer.from(input);
50
88
  const data = Buffer.concat([this.tail, input]);
51
89
  const size = Math.floor(data.length / 2) * 2;
52
90
  this.tail = Buffer.from(data.subarray(size));
53
91
  if (final && this.tail.length)
54
92
  throw new RimeAudioFormatError("Incomplete final PCM sample frame");
55
- if (this.profile === AudioFormat.PCM_24000)
93
+ if (this.profile === AudioFormat.PCM ||
94
+ this.profile === AudioFormat.PCM_24000)
56
95
  return data.subarray(0, size);
57
96
  const result = [];
58
97
  for (let i = 0; i < size; i += 2) {
@@ -0,0 +1,3 @@
1
+ export declare const maxBufferBytes: number;
2
+ /** Validate mono PCM16 WAV and replace unknown streaming chunk lengths. */
3
+ export declare function finalizeWav(data: Buffer, sampleRate: number, requestId: string | null): void;