@rimelabs/sdk 0.1.0-alpha.2 → 0.1.0-alpha.20

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +142 -0
  2. package/LICENSE-PRISM +201 -0
  3. package/NOTICE-PRISM +9 -0
  4. package/README.md +278 -114
  5. package/dist/auth.d.ts +9 -3
  6. package/dist/auth.js +12 -4
  7. package/dist/cancellation.d.ts +6 -0
  8. package/dist/cancellation.js +54 -0
  9. package/dist/client.d.ts +24 -23
  10. package/dist/client.js +41 -92
  11. package/dist/grpc.d.ts +27 -0
  12. package/dist/grpc.js +175 -0
  13. package/dist/index.d.ts +10 -2
  14. package/dist/index.js +6 -2
  15. package/dist/pcm.d.ts +21 -0
  16. package/dist/pcm.js +52 -0
  17. package/dist/realtime/async.d.ts +23 -0
  18. package/dist/realtime/async.js +87 -0
  19. package/dist/realtime/audio.d.ts +2 -0
  20. package/dist/realtime/audio.js +11 -0
  21. package/dist/realtime/client.d.ts +12 -0
  22. package/dist/realtime/client.js +206 -0
  23. package/dist/realtime/conversation.d.ts +47 -0
  24. package/dist/realtime/conversation.js +309 -0
  25. package/dist/realtime/protocol.d.ts +84 -0
  26. package/dist/realtime/protocol.js +397 -0
  27. package/dist/realtime/session.d.ts +69 -0
  28. package/dist/realtime/session.js +744 -0
  29. package/dist/realtime/transport.d.ts +16 -0
  30. package/dist/realtime/transport.js +83 -0
  31. package/dist/realtime/types.d.ts +240 -0
  32. package/dist/realtime/types.js +10 -0
  33. package/dist/stt/audio.d.ts +15 -0
  34. package/dist/stt/audio.js +46 -0
  35. package/dist/stt/client.d.ts +17 -0
  36. package/dist/stt/client.js +121 -0
  37. package/dist/stt/policy.d.ts +11 -0
  38. package/dist/stt/policy.js +11 -0
  39. package/dist/stt/protocol.d.ts +14 -0
  40. package/dist/stt/protocol.js +76 -0
  41. package/dist/stt/queue.d.ts +16 -0
  42. package/dist/stt/queue.js +45 -0
  43. package/dist/stt/stream.d.ts +48 -0
  44. package/dist/stt/stream.js +224 -0
  45. package/dist/stt/transport.d.ts +15 -0
  46. package/dist/stt/transport.js +94 -0
  47. package/dist/stt/types.d.ts +51 -0
  48. package/dist/stt/types.js +1 -0
  49. package/dist/{audio.d.ts → tts/audio.d.ts} +9 -0
  50. package/dist/{audio.js → tts/audio.js} +44 -4
  51. package/dist/tts/buffer.d.ts +3 -0
  52. package/dist/tts/buffer.js +53 -0
  53. package/dist/tts/client.d.ts +53 -0
  54. package/dist/tts/client.js +151 -0
  55. package/dist/tts/lexicon.d.ts +7 -0
  56. package/dist/tts/lexicon.js +23 -0
  57. package/dist/{policy.d.ts → tts/policy.d.ts} +2 -3
  58. package/dist/tts/policy.js +41 -0
  59. package/dist/{stream.d.ts → tts/stream.d.ts} +15 -2
  60. package/dist/{stream.js → tts/stream.js} +54 -30
  61. package/dist/tts/timestamps.d.ts +24 -0
  62. package/dist/tts/timestamps.js +46 -0
  63. package/dist/{transport.d.ts → tts/transport.d.ts} +10 -13
  64. package/dist/tts/transport.js +133 -0
  65. package/dist/validation.d.ts +5 -0
  66. package/dist/validation.js +28 -0
  67. package/docs/realtime.md +426 -0
  68. package/docs/stt.md +186 -0
  69. package/docs/tts.md +284 -0
  70. package/package.json +14 -12
  71. package/dist/policy.js +0 -82
  72. package/dist/sentences.d.ts +0 -21
  73. package/dist/sentences.js +0 -142
  74. package/dist/transport.js +0 -223
  75. package/vendor/LICENSE.blingfire +0 -21
  76. package/vendor/README.md +0 -11
  77. package/vendor/blingfire.cjs +0 -4
  78. package/vendor/blingfire.upstream.cjs +0 -1
  79. package/vendor/blingfire.wasm +0 -0
  80. /package/dist/{queue.d.ts → tts/queue.d.ts} +0 -0
  81. /package/dist/{queue.js → tts/queue.js} +0 -0
package/docs/stt.md ADDED
@@ -0,0 +1,186 @@
1
+ # Speech recognition
2
+
3
+ ## Complete recordings
4
+
5
+ For a complete recording, use the native unary `Transcribe` RPC through the SDK.
6
+ Pass **complete WAV bytes, including the header**, up to **16 MiB (16,777,216
7
+ bytes)**. PCM16 mono 16 kHz WAV is the portable format. The SDK sends the file
8
+ unchanged; it does not decode, resample, or add a header. The service validates
9
+ its encoding and the supplied language and recognition hints.
10
+
11
+ The result is a `Transcription` containing final text, resolved language, and a
12
+ request ID when available. Silence may return empty text. There are no partial
13
+ updates or automatic retries. TTS client settings do not apply. An optional
14
+ overall timeout includes connection and upload; the SDK also bounds connection
15
+ time and the unary request (120 seconds). Timeout, cancellation, and service
16
+ errors use the existing SDK error categories. A request ID is included when the
17
+ transport has made it available.
18
+
19
+ ```typescript
20
+ import { readFile } from "node:fs/promises";
21
+ import { Rime } from "@rimelabs/sdk";
22
+
23
+ const client = new Rime();
24
+ try {
25
+ const result = await client.stt.transcribe(await readFile("utterance.wav"), {
26
+ language: "en",
27
+ mode: "written",
28
+ contextTerms: ["Super-G", "USOC"],
29
+ timeout: 120,
30
+ });
31
+ console.log(result.text, result.language, result.requestId);
32
+ } finally {
33
+ await client.close();
34
+ }
35
+ ```
36
+
37
+ `TranscribeOptions` also accepts an `AbortSignal` as `signal`. Aborting it or
38
+ closing the client cancels pending requests with `RimeCancelledError`.
39
+
40
+ ## Streaming recognition
41
+
42
+ `client.stt.stream` transcribes one utterance from an async source of `Uint8Array` chunks (including Node buffers).
43
+ The SDK handles the gRPC protocol, authentication, audio conversion, transcript
44
+ revisions, backpressure, and cancellation. Your application decides when speech
45
+ ends by exhausting the source.
46
+
47
+ ```typescript
48
+ import { createReadStream } from "node:fs";
49
+ import { Rime } from "@rimelabs/sdk";
50
+
51
+ async function* audioChunks() {
52
+ yield* createReadStream("utterance.pcm");
53
+ }
54
+
55
+ const client = new Rime();
56
+ try {
57
+ const transcript = client.stt.stream(audioChunks(), {
58
+ language: "en",
59
+ inputFormat: { sampleRate: 16000, channels: 1 },
60
+ mode: "written",
61
+ contextTerms: ["Super-G", "USOC"],
62
+ timeout: 120,
63
+ });
64
+ for await (const update of transcript) {
65
+ if (update.kind === "partial") console.log("Current transcript:", update.text);
66
+ else if (update.kind === "final")
67
+ console.log("Final:", update.text, "Language:", update.language);
68
+ }
69
+ console.log("Request:", transcript.requestId);
70
+ } finally {
71
+ await client.close();
72
+ }
73
+ ```
74
+
75
+ The runnable [example](../examples/stt/stream.mjs) reads a PCM file
76
+ without loading it all into memory. Use Node.js 22 or later; browsers are not supported.
77
+ Open files inside the async generator so file errors are reported through the
78
+ SDK and resources are opened only after the service accepts the utterance.
79
+
80
+ ## Append-fragment streaming
81
+
82
+ Choose `outputContract: "append_fragments"` on
83
+ `client.stt.stream`. The default is `"revised_hypotheses"`; `transcribe()` does not
84
+ accept an output contract. Both streaming modes use gRPC only.
85
+
86
+ ```javascript
87
+ let text = "";
88
+ const transcript = client.stt.stream(audioChunks(), {
89
+ language: "en", outputContract: "append_fragments",
90
+ });
91
+ for await (const update of transcript) {
92
+ if (update.kind === "delta") text += update.text;
93
+ else if (update.kind === "final") console.log(update.text);
94
+ }
95
+ ```
96
+
97
+ `TranscriptionDelta` is a permanent fragment, not a replacement partial. The
98
+ server recognizes independent five-second audio windows; fragments after the
99
+ first include newline separators. Do not strip, add separators, or drop deltas.
100
+ The final text must match their concatenation. Empty speech can produce no
101
+ updates before an empty final. A final is delivered only after `done` and an OK
102
+ gRPC status, so a connection failure never promotes accumulated fragments to a
103
+ successful result.
104
+
105
+ Append mode suits longer recordings and consumers that cannot revise text. Send
106
+ recordings as fast as backpressure permits. It does not enable unlimited-length
107
+ streams: the service's 16 MiB audio and 120-second total-call limits still apply.
108
+ Split larger recordings into separate streams. Live voice agents usually prefer
109
+ revised hypotheses for earlier provisional text and corrections across windows.
110
+
111
+ ## Options
112
+
113
+ | Option | Meaning |
114
+ | --- | --- |
115
+ | `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. |
116
+ | `mode` | `"written"` (default) or `"verbatim"`. Formatting intent, not a guarantee of exact tokens. |
117
+ | `contextTerms` | Sequence of recognition hints, copied unchanged and in order. The service validates terms and prompt budgets. |
118
+ | `outputContract` | `"revised_hypotheses"` (default) or `"append_fragments"`. Streaming only. |
119
+ | `inputFormat` | `PCMFormat`: signed little-endian PCM16 at 8, 16, 24 or 48 kHz, mono or interleaved stereo. Defaults to 16 kHz mono. |
120
+ | `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. |
121
+
122
+ Input contains raw samples, with no WAV header or compressed codec. Chunks may
123
+ split sample frames; the SDK carries incomplete bytes into the next chunk. A
124
+ truncated final frame raises `RimeAudioFormatError`, without committing the
125
+ utterance. Each gRPC audio payload is at most 64 KiB of 16 kHz mono PCM16.
126
+
127
+ Recognition connects to `stt.api.rime.ai:443` using TLS and the same API key as
128
+ the other features. No endpoint configuration is needed.
129
+ The existing `model`, `endpoint` and client `timeout` settings remain TTS settings.
130
+ There is no STT voice/model selector; the service selects its recognizer.
131
+
132
+ ## Results and completion
133
+
134
+ `TranscriptionPartial`, `TranscriptionDelta`, and `TranscriptionFinal` are discriminated types exported from `@rimelabs/sdk`. In the default revised-hypotheses mode, every update contains the complete current `text`.
135
+ **Replace the previous transcript. Do not concatenate partials.** Later partials
136
+ can change earlier words. `TranscriptionFinal.language` is the canonical language
137
+ selected by the service.
138
+
139
+ When upgrading, `TranscriptionUpdate` now includes `TranscriptionDelta`. Update
140
+ exhaustive handlers to handle `kind: "delta"`, even if you keep the default
141
+ revised-hypotheses contract. Only finals have `language`: check
142
+ `update.kind === "final"` before reading it, rather than assuming every
143
+ non-partial update is final. Append deltas exactly; replace text with partials
144
+ or the confirmed final.
145
+
146
+ The source is not consumed until the server accepts the configuration. Audio
147
+ upload and transcript reading run concurrently. Decoding currently uses cumulative
148
+ two-second audio windows, so a partial is not promised for each audio packet.
149
+ Silence can finish successfully with empty final text and no partials.
150
+
151
+ Exhausting the source half-closes gRPC input. The SDK continues reading and emits
152
+ one final result only after a matching `done` and successful gRPC completion.
153
+ EOF, an error status, or cancellation cannot turn a partial into a final result.
154
+ No audio is automatically replayed after a connection failure.
155
+
156
+ ## Lifetime and limits
157
+
158
+ Construction is lazy: starting iteration begins work. Consume each stream with
159
+ one reader. Breaking a `for await` loop calls the iterator's `return` and cancels
160
+ unfinished work. `await transcript.cancel()` and async disposal also cancel it.
161
+ Pass an `AbortSignal` through the stream's `signal` option to cancel just that
162
+ utterance. Client close cancels all STT, TTS and realtime operations; cancelling
163
+ one stream leaves its siblings running.
164
+
165
+ The SDK bounds the transcript queue to 16 updates. In revised-hypotheses mode,
166
+ each replacement update is limited to 1 MiB of UTF-8 text; sizes are not summed
167
+ across revisions. In append-fragments mode, the concatenated text across all
168
+ deltas is limited to 1 MiB. The final transcript is also limited to 1 MiB in both
169
+ modes. gRPC responses are bounded to 1 MiB plus 64 KiB for the protobuf envelope.
170
+ Slow consumers apply backpressure.
171
+ Internal connection and acceptance waits are 10 seconds; completion after source
172
+ exhaustion is bounded to 120 seconds. The completion timer stops after successful
173
+ gRPC completion. The optional overall timeout remains active until the final
174
+ result is consumed. An outstanding source `next()` cannot be forcibly stopped by
175
+ JavaScript; the SDK stops waiting for it and attempts `return()` for at most two
176
+ seconds. Sources that own devices should support cancellation themselves.
177
+
178
+ Server input-idle, utterance-size, request-duration and model-context limits still
179
+ apply. The current wire limit is 16 MiB per utterance; model context can impose a
180
+ smaller limit depending on audio and recognition terms. There is no SDK VAD,
181
+ server endpointing, word timing, diarization, or file-format detection.
182
+
183
+ Failures use the shared SDK error hierarchy: caller/input errors, cancellation,
184
+ authentication, permission, resource limits, timeouts, transient availability and
185
+ protocol failures stay distinct. `requestId` is retained on streams and failures
186
+ when the server supplies it. Keep it when reporting a problem.
package/docs/tts.md ADDED
@@ -0,0 +1,284 @@
1
+ # TTS for Node.js
2
+
3
+ Send text as it becomes available; Rime automatically combines fragments into
4
+ sentences for speech. For nonblank input, ending the text stream flushes any
5
+ remaining text, even without final punctuation. Pausing the stream may delay
6
+ speech for an unfinished sentence.
7
+
8
+ If the source ends without any non-whitespace text, audio iteration rejects with
9
+ `RimeInputError`.
10
+
11
+ The SDK sends text in order with its whitespace preserved and streams audio back.
12
+ Your application owns playback. Start with the complete WAV example in the
13
+ [package quick start](../README.md#tts-save-speech).
14
+
15
+ ## String input
16
+
17
+ Use `client.tts.synthesize(text)` for one nonblank string. It returns an audio
18
+ stream; it does not wait for all audio to be generated. Do not await this call.
19
+ Use the audio loop and cleanup shown in the package quick start.
20
+
21
+ ## Stream incoming text
22
+
23
+ Use `client.tts.stream(chunks)` for an async iterable of strings. A plain string
24
+ belongs in `synthesize`, not `stream`. The source can supply partial sentences:
25
+
26
+ ```javascript
27
+ async function* text() {
28
+ yield "Hello. ";
29
+ yield "This text arrives in separate chunks.";
30
+ }
31
+ ```
32
+
33
+ Use `client.tts.stream(text())` with the same file write and cleanup.
34
+ Do not await `tts.stream()`; the first iterator read starts work. The quick start
35
+ collects chunks before writing a WAV file.
36
+
37
+ ## Configuration
38
+
39
+ Select Mist v3 with `new Rime({ model: "mistv3" })`. Mist v1 and v2 are not supported.
40
+
41
+ | Model | Standard endpoint | Default voice |
42
+ | --- | --- | --- |
43
+ | `coda` | `coda.api.rime.ai:443` | `clementine` |
44
+ | `mistv3` | `mist.api.rime.ai:443` | `astra` |
45
+
46
+ ### Client options
47
+
48
+ | `new Rime({ ... })` option | Default | Meaning |
49
+ | --- | --- | --- |
50
+ | `apiKey` | `RIME_API_KEY` | API key; an explicit value overrides the environment |
51
+ | `model` | `"coda"` | Model for speech and discovery |
52
+ | `endpoint` | Model's standard endpoint | Custom hostname and optional port for speech and discovery |
53
+ | `timeout` | No overall deadline | Positive seconds; `null` disables the overall deadline |
54
+
55
+ Authentication uses bearer tokens over TLS. The SDK does not load `.env` files.
56
+
57
+ Custom deployments use `new Rime({ model: "coda", endpoint: "host:8443" })`.
58
+ Omit the scheme and path. TLS is required; the default port is `443`.
59
+ Model defaults still apply.
60
+
61
+ ### Synthesis options
62
+
63
+ `synthesize` and `synthesizeToBuffer` require a nonblank string; `stream` requires
64
+ an async iterable of strings. All three accept the following settings:
65
+
66
+ | Option | Default | Meaning |
67
+ | --- | --- | --- |
68
+ | `voice` | Model's default voice | Voice name from `client.voices.list()` |
69
+ | `language` | `"en"` | Language code |
70
+ | `audioFormat` | `AudioFormat.PCM` (24 kHz) | Output encoding, imported from `@rimelabs/sdk` |
71
+ | `sampleRate` | `24000` (or fixed preset rate) | Requested output rate: `8000`, `16000`, or `24000` Hz |
72
+ | `timeScaleFactor` | Omitted (normal duration) | Duration multiplier from `0.4` to `2.5`; below 1 is faster, above 1 is slower |
73
+ | `timestamps` | `false` | Request final word timestamps; requires `model: "mistv3"` |
74
+ | `customLexicon` | `[]` | Array of pronunciation overrides for this request; requires Coda |
75
+ | `timeout` | Client setting | Seconds; pass `null` to disable the overall deadline |
76
+
77
+ ### Audio formats
78
+
79
+ All output is mono. PCM and WAV use signed 16-bit little-endian samples.
80
+
81
+ | `AudioFormat` | Encoding | Requested rate |
82
+ | --- | --- | --- |
83
+ | `PCM` | Headerless PCM16 | 8, 16, or 24 kHz |
84
+ | `WAV` | PCM16 WAV | 8, 16, or 24 kHz |
85
+ | `MP3` | MP3 (`audio/mpeg`) | 8, 16, or 24 kHz; encoded headers determine the actual rate |
86
+ | `PCM_24000` | Headerless PCM16 compatibility preset | Fixed 24 kHz |
87
+ | `MULAW_8000` | G.711 mu-law compatibility preset | Fixed 8 kHz |
88
+
89
+ PCM/WAV sample rates are requested from the server; the SDK does not resample
90
+ these formats. The existing mu-law preset still converts 24 kHz PCM locally.
91
+ An explicit rate conflicting with a fixed preset fails locally. For configurable
92
+ formats, `audio.format.sampleRate` is zero; use `audio.sampleRate` for the rate requested
93
+ by the operation. Existing default streams still report `PCM_24000` as their format.
94
+
95
+ PCM chunks contain complete sample frames. WAV and MP3 chunks are fragments of
96
+ one encoded stream, not independent files. Streaming WAV may contain unknown
97
+ lengths in its header; use `synthesizeToBuffer` for a completed IVR file.
98
+
99
+ ### Complete audio in memory
100
+
101
+ Await `synthesizeToBuffer` to get all audio bytes. It uses the same streaming RPC,
102
+ waits for successful completion, and finalizes WAV file lengths. On failure it
103
+ raises an error and releases the stream; it never returns partial audio.
104
+ The output limit is fixed at **64 MiB (67,108,864 bytes)** per call, including
105
+ file headers. Exceeding it raises `RimeResourceLimitError` and returns no partial
106
+ audio. For mono PCM16/WAV, this is approximately 70 minutes at 8 kHz, 35 minutes
107
+ at 16 kHz, or 23 minutes at 24 kHz. Use streaming synthesis for larger outputs.
108
+ Collection can temporarily use more memory than the output limit.
109
+ Timeouts and client shutdown still apply. Use the streaming API to retrieve word timestamps.
110
+
111
+ ```javascript
112
+ import { writeFile } from "node:fs/promises";
113
+ import { AudioFormat, Rime } from "@rimelabs/sdk";
114
+
115
+ const client = new Rime();
116
+ try {
117
+ const audio = await client.tts.synthesizeToBuffer("Thank you for calling.", {
118
+ audioFormat: AudioFormat.WAV, sampleRate: 16000, timeScaleFactor: 0.9,
119
+ });
120
+ await writeFile("greeting.wav", audio);
121
+ } finally {
122
+ await client.close();
123
+ }
124
+ ```
125
+
126
+ ### Timeouts
127
+
128
+ An overall deadline includes pauses between reads. Disabling it leaves internal
129
+ connection and progress limits active. Discovery allows at most 10 seconds;
130
+ a shorter supplied timeout takes precedence. Omit an operation's `timeout` or
131
+ pass `undefined` to inherit the client setting.
132
+
133
+ ## Custom pronunciations (Coda)
134
+
135
+ Pass words or phrases and their space-separated X-SAMPA pronunciations:
136
+
137
+ ```javascript
138
+ import { writeFile } from "node:fs/promises";
139
+ import { Rime, RimeInputError } from "@rimelabs/sdk";
140
+
141
+ const lexicon = [{ spelling: "hello", pronunciation: 'h @ . " l oU' }];
142
+ const client = new Rime({ model: "coda", timeout: 60 });
143
+ try {
144
+ await writeFile("speech.pcm", client.tts.synthesize("Hello world.", {
145
+ language: "en",
146
+ customLexicon: lexicon,
147
+ }));
148
+ } catch (error) {
149
+ if (error instanceof RimeInputError) {
150
+ console.error(`Invalid synthesis request: ${error.message}; requestId=${error.requestId}`);
151
+ }
152
+ throw error;
153
+ } finally {
154
+ await client.close();
155
+ }
156
+ ```
157
+
158
+ TypeScript callers can import the `PronunciationEntry` type from `@rimelabs/sdk`.
159
+ The SDK copies the array and entries when `tts.synthesize()` or `tts.stream()`
160
+ is called. The same lexicon applies to the entire request, including an async
161
+ text source; subsequent requests have their own options. Omit the option or
162
+ pass an empty array for default pronunciation.
163
+
164
+ The service matches whole words or phrases case-insensitively and applies the
165
+ pronunciation after text normalization. Longest matches take precedence; the
166
+ last entry for a repeated spelling wins. Inline `pronounce(...)` directives are
167
+ not supported.
168
+
169
+ Coda accepts custom pronunciations in German, English, Spanish, French,
170
+ Italian, and Portuguese, with up to 500 entries per request. The service validates
171
+ supported languages and pronunciation rules. Mist rejects nonempty lexicons.
172
+
173
+ Malformed option types and invalid Unicode in lexicon fields throw locally.
174
+ The service rejects unsupported models or languages, oversized lexicons, and
175
+ ill-formed entries with `RimeInputError`
176
+ before producing audio. Catch it around consuming the stream. Its `message`
177
+ preserves the offending spelling and checks such as `no-primary-stress` or
178
+ `unknown-phone`, and `requestId` identifies the request. The whole request fails;
179
+ entries are never silently skipped or repaired.
180
+
181
+ ## Word timestamps (Mist v3)
182
+
183
+ Enable timestamps on a synthesis, consume all audio, then read its result:
184
+
185
+ ```javascript
186
+ import { writeFile } from "node:fs/promises";
187
+ import { Rime } from "@rimelabs/sdk";
188
+
189
+ const client = new Rime({ model: "mistv3", timeout: 60 });
190
+ try {
191
+ const audio = client.tts.synthesize("Hello world.", { timestamps: true });
192
+ await writeFile("speech.pcm", audio);
193
+ const result = await audio.timestamps();
194
+ if (result.status.code === 0) {
195
+ for (const word of result.spans) {
196
+ console.log(word.text, word.start, word.end);
197
+ }
198
+ } else {
199
+ console.log("Timestamps unavailable:", result.status.code, result.status.message);
200
+ }
201
+ } finally {
202
+ await client.close();
203
+ }
204
+ ```
205
+
206
+ `TimestampResult`, `TimestampStatus`, and `WordTimestamp` are exported types from
207
+ `@rimelabs/sdk`. The result is immutable. `spans` is an array of words with `text`,
208
+ `start`, and `end`; times are seconds from the beginning of the entire synthesis,
209
+ including when text arrives incrementally. Resampling to `MULAW_8000` does not
210
+ change these times. Words reflect normalized spoken text, so a number may become
211
+ several words. The model supplies the timings; the SDK does not estimate them.
212
+
213
+ Timestamps arrive once, after generation finishes. They are not incremental
214
+ events for live interruption handling. `timestamps()` never consumes audio or
215
+ waits for you to consume it: calling before successful iteration finishes, or
216
+ without enabling timestamps, rejects with `RimeInputError`. Results remain
217
+ accessible after a successfully consumed stream or its client is closed.
218
+
219
+ `status.code` is a numeric `google.rpc.Code`: `0` is success. Nonzero codes such as
220
+ `12` (unimplemented) or `4` (deadline exceeded) carry an explanation and no spans;
221
+ they do not fail successful audio. Missing or malformed timestamp responses reject
222
+ with `RimeStreamError` only when reading timestamps. A synthesis error or
223
+ cancellation also prevents reading a successful timestamp result. Enabling
224
+ timestamps with Coda throws `RimeInputError` before any request is sent.
225
+
226
+ ## Discover voices and languages
227
+
228
+ List voices and languages while the client is open:
229
+
230
+ ```javascript
231
+ const voices = await client.voices.list({ language: "en" });
232
+ const languages = await client.languages.list();
233
+ console.log(voices, languages);
234
+ ```
235
+
236
+ Both return string arrays and accept `timeout`. Omit `language` to disable filtering.
237
+
238
+ ## Errors and cancellation
239
+
240
+ Report SDK errors and request IDs while closing the client on failure:
241
+
242
+ ```javascript
243
+ import { writeFile } from "node:fs/promises";
244
+ import { Rime, RimeError } from "@rimelabs/sdk";
245
+
246
+ let client;
247
+ try {
248
+ client = new Rime();
249
+ await writeFile("speech.pcm", client.tts.synthesize("Hello. This is Rime."));
250
+ } catch (error) {
251
+ if (error instanceof RimeError) {
252
+ console.error(`Speech failed: ${error.message}; requestId=${error.requestId}`);
253
+ }
254
+ throw error;
255
+ } finally {
256
+ await client?.close();
257
+ }
258
+ ```
259
+
260
+ Each SDK error inherits from `RimeError`:
261
+
262
+ | Error | Meaning |
263
+ | --- | --- |
264
+ | `RimeAuthenticationError` | Missing or rejected credentials |
265
+ | `RimePermissionError` | Access denied |
266
+ | `RimeInputError` | Invalid input or client use |
267
+ | `RimeResourceLimitError` | Service resource limit reached |
268
+ | `RimeUnavailableError` | Service unavailable |
269
+ | `RimeTimeoutError` | Overall or internal deadline reached |
270
+ | `RimeAudioFormatError` | Unsupported or unexpected audio format |
271
+ | `RimeCancelledError` | Operation cancelled |
272
+ | `RimeStreamError` | Other stream or transport failure |
273
+
274
+ Service errors retain their diagnostic message and request ID when provided.
275
+ Use the exception type for programmatic handling; message wording may change.
276
+
277
+ Partial audio can arrive before an error. Output is complete only after successful
278
+ iteration. Synthesis is not retried automatically.
279
+
280
+ Keep cleanup when stopping early. A `for await` loop exit cancels the stream.
281
+ For manual reads, call `await audio.cancel()` in `finally`. `await client.close()`
282
+ stops all client work. Both calls are safe to repeat; both objects support
283
+ `Symbol.asyncDispose`. Cancellation stops the RPC and limits cleanup time, but
284
+ cannot force your text source to finish an unrelated promise.
package/package.json CHANGED
@@ -1,13 +1,8 @@
1
1
  {
2
2
  "name": "@rimelabs/sdk",
3
- "version": "0.1.0-alpha.2",
4
- "description": "Rime streaming text-to-speech SDK for Node.js",
3
+ "version": "0.1.0-alpha.20",
4
+ "description": "Rime streaming TTS, STT and Prism realtime SDK for Node.js",
5
5
  "license": "MIT",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/rimelabs/rime-sdk.git",
9
- "directory": "typescript"
10
- },
11
6
  "type": "module",
12
7
  "engines": {
13
8
  "node": ">=22"
@@ -21,8 +16,10 @@
21
16
  },
22
17
  "files": [
23
18
  "dist",
24
- "vendor",
19
+ "docs",
25
20
  "LICENSE",
21
+ "LICENSE-PRISM",
22
+ "NOTICE-PRISM",
26
23
  "README.md",
27
24
  "CHANGELOG.md"
28
25
  ],
@@ -31,16 +28,21 @@
31
28
  "check": "tsc -p tsconfig.json --noEmit",
32
29
  "test": "npm run build && node --test tests/*.test.mjs",
33
30
  "prepack": "npm run build",
34
- "lint": "prettier --check src tests"
31
+ "lint": "prettier --check src tests examples/tts examples/realtime examples/common examples/browser examples/tests examples/stt"
35
32
  },
36
33
  "dependencies": {
37
34
  "@bufbuild/protobuf": "^2.11.0",
38
35
  "@grpc/grpc-js": "^1.14.0",
39
- "@rimelabs/api": "0.0.1"
36
+ "@rimelabs/api": "0.4.0",
37
+ "ws": "^8.22.0"
40
38
  },
41
39
  "devDependencies": {
42
40
  "@types/node": "^22.0.0",
41
+ "@types/ws": "^8.18.2",
42
+ "ajv": "^8.20.0",
43
43
  "prettier": "3.9.8",
44
- "typescript": "^5.9.0"
45
- }
44
+ "typescript": "^5.9.0",
45
+ "yaml": "^2.9.1"
46
+ },
47
+ "homepage": "https://docs.rime.ai/"
46
48
  }
package/dist/policy.js DELETED
@@ -1,82 +0,0 @@
1
- import { RimeInputError } from "./errors.js";
2
- const codaHostname = "coda.api.rime.ai";
3
- export const policy = {
4
- target: `${codaHostname}:443`,
5
- audience: codaHostname,
6
- defaultVoice: "clementine",
7
- exchangeUrl: "https://themis.api.rime.ai/v1/token",
8
- sentenceBytes: 65536,
9
- sourceChars: 1024,
10
- outputBytes: 96000,
11
- outputChunkBytes: 9600,
12
- receiveBytes: 4194304,
13
- authTimeout: 10,
14
- connectionTimeout: 10,
15
- firstAudioTimeout: 30,
16
- progressTimeout: 60,
17
- discoveryTimeout: 10,
18
- cleanupTimeout: 2,
19
- };
20
- export function resolve(model, endpoint) {
21
- const deployment = { ...policy };
22
- if (model === "mistv3") {
23
- const hostname = "mist.api.rime.ai";
24
- deployment.target = `${hostname}:443`;
25
- deployment.audience = hostname;
26
- deployment.defaultVoice = "astra";
27
- }
28
- else if (model !== "coda") {
29
- throw new RimeInputError("model must be 'coda' or 'mistv3'");
30
- }
31
- if (endpoint === undefined || endpoint === null)
32
- return deployment;
33
- const error = "endpoint must be a hostname with an optional port (1-65535), without a scheme or path";
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;
59
- }
60
- export function nonempty(value, name) {
61
- if (typeof value !== "string" || !value.trim())
62
- throw new RimeInputError(`${name} must be a non-empty string`);
63
- return value;
64
- }
65
- export function abortable(promise, signal) {
66
- if (signal.aborted)
67
- return Promise.reject(signal.reason);
68
- return new Promise((resolve, reject) => {
69
- const abort = () => {
70
- signal.removeEventListener("abort", abort);
71
- reject(signal.reason);
72
- };
73
- signal.addEventListener("abort", abort, { once: true });
74
- Promise.resolve(promise).then((value) => {
75
- signal.removeEventListener("abort", abort);
76
- resolve(value);
77
- }, (error) => {
78
- signal.removeEventListener("abort", abort);
79
- reject(error);
80
- });
81
- });
82
- }
@@ -1,21 +0,0 @@
1
- interface BlingFire {
2
- ready: Promise<BlingFire>;
3
- _malloc(n: number): number;
4
- _free(p: number): void;
5
- _TextToSentences(input: number, length: number, output: number, capacity: number): number;
6
- HEAPU8: Uint8Array;
7
- }
8
- export declare const ready: Promise<BlingFire>;
9
- export declare class SentenceBuffer {
10
- private limit;
11
- private pending;
12
- private committed;
13
- private parts;
14
- private untilScan;
15
- private sincePunctuation;
16
- private highSurrogate;
17
- constructor(limit: number);
18
- feed(fragment: string, final?: boolean): Generator<string>;
19
- private scan;
20
- }
21
- export {};