@rimelabs/sdk 0.1.0-alpha.2 → 0.1.0-alpha.21
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 +155 -0
- package/LICENSE-PRISM +201 -0
- package/NOTICE-PRISM +9 -0
- package/README.md +279 -114
- package/dist/auth.d.ts +9 -3
- package/dist/auth.js +12 -4
- package/dist/cancellation.d.ts +6 -0
- package/dist/cancellation.js +54 -0
- package/dist/client.d.ts +24 -23
- package/dist/client.js +41 -92
- package/dist/grpc.d.ts +27 -0
- package/dist/grpc.js +175 -0
- package/dist/index.d.ts +10 -2
- package/dist/index.js +6 -2
- package/dist/pcm.d.ts +21 -0
- package/dist/pcm.js +52 -0
- package/dist/realtime/async.d.ts +23 -0
- package/dist/realtime/async.js +87 -0
- package/dist/realtime/audio.d.ts +2 -0
- package/dist/realtime/audio.js +11 -0
- package/dist/realtime/client.d.ts +12 -0
- package/dist/realtime/client.js +139 -0
- package/dist/realtime/conversation.d.ts +47 -0
- package/dist/realtime/conversation.js +321 -0
- package/dist/realtime/faults.d.ts +2 -0
- package/dist/realtime/faults.js +25 -0
- package/dist/realtime/protocol.d.ts +99 -0
- package/dist/realtime/protocol.js +432 -0
- package/dist/realtime/session.d.ts +79 -0
- package/dist/realtime/session.js +808 -0
- package/dist/realtime/settings.d.ts +11 -0
- package/dist/realtime/settings.js +142 -0
- package/dist/realtime/transport.d.ts +16 -0
- package/dist/realtime/transport.js +86 -0
- package/dist/realtime/types.d.ts +292 -0
- package/dist/realtime/types.js +12 -0
- package/dist/stt/audio.d.ts +15 -0
- package/dist/stt/audio.js +46 -0
- package/dist/stt/client.d.ts +17 -0
- package/dist/stt/client.js +121 -0
- package/dist/stt/policy.d.ts +11 -0
- package/dist/stt/policy.js +11 -0
- package/dist/stt/protocol.d.ts +14 -0
- package/dist/stt/protocol.js +76 -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 +15 -0
- package/dist/stt/transport.js +94 -0
- package/dist/stt/types.d.ts +51 -0
- package/dist/stt/types.js +1 -0
- package/dist/{audio.d.ts → tts/audio.d.ts} +9 -0
- package/dist/{audio.js → tts/audio.js} +44 -4
- package/dist/tts/buffer.d.ts +3 -0
- package/dist/tts/buffer.js +53 -0
- package/dist/tts/client.d.ts +53 -0
- package/dist/tts/client.js +151 -0
- package/dist/tts/lexicon.d.ts +7 -0
- package/dist/tts/lexicon.js +23 -0
- package/dist/{policy.d.ts → tts/policy.d.ts} +2 -3
- package/dist/tts/policy.js +41 -0
- package/dist/{stream.d.ts → tts/stream.d.ts} +15 -2
- package/dist/{stream.js → tts/stream.js} +54 -30
- package/dist/tts/timestamps.d.ts +24 -0
- package/dist/tts/timestamps.js +46 -0
- package/dist/{transport.d.ts → tts/transport.d.ts} +10 -13
- package/dist/tts/transport.js +133 -0
- package/dist/validation.d.ts +5 -0
- package/dist/validation.js +28 -0
- package/docs/realtime.md +537 -0
- package/docs/stt.md +186 -0
- package/docs/tts.md +284 -0
- package/package.json +14 -12
- package/dist/policy.js +0 -82
- package/dist/sentences.d.ts +0 -21
- package/dist/sentences.js +0 -142
- package/dist/transport.js +0 -223
- package/vendor/LICENSE.blingfire +0 -21
- package/vendor/README.md +0 -11
- package/vendor/blingfire.cjs +0 -4
- package/vendor/blingfire.upstream.cjs +0 -1
- package/vendor/blingfire.wasm +0 -0
- /package/dist/{queue.d.ts → tts/queue.d.ts} +0 -0
- /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.
|
|
4
|
-
"description": "Rime streaming
|
|
3
|
+
"version": "0.1.0-alpha.21",
|
|
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
|
-
"
|
|
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
|
|
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
|
-
}
|
package/dist/sentences.d.ts
DELETED
|
@@ -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 {};
|