@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.
- package/CHANGELOG.md +142 -0
- package/LICENSE-PRISM +201 -0
- package/NOTICE-PRISM +9 -0
- package/README.md +278 -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 +206 -0
- package/dist/realtime/conversation.d.ts +47 -0
- package/dist/realtime/conversation.js +309 -0
- package/dist/realtime/protocol.d.ts +84 -0
- package/dist/realtime/protocol.js +397 -0
- package/dist/realtime/session.d.ts +69 -0
- package/dist/realtime/session.js +744 -0
- package/dist/realtime/transport.d.ts +16 -0
- package/dist/realtime/transport.js +83 -0
- package/dist/realtime/types.d.ts +240 -0
- package/dist/realtime/types.js +10 -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 +426 -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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { connect as connectGrpc } from "../grpc.js";
|
|
2
|
+
export { metadata } from "../grpc.js";
|
|
3
|
+
import { BidiCall, serializer, deserializer, requestId, nativeError, } from "../grpc.js";
|
|
4
|
+
export { rpcError } from "../grpc.js";
|
|
5
|
+
import * as grpc from "@grpc/grpc-js";
|
|
6
|
+
import { create, } from "@bufbuild/protobuf";
|
|
7
|
+
import * as schema from "@rimelabs/api";
|
|
8
|
+
import { policy } from "./policy.js";
|
|
9
|
+
import { abortable } from "../cancellation.js";
|
|
10
|
+
import * as errors from "../errors.js";
|
|
11
|
+
import { TimestampTrailer } from "./timestamps.js";
|
|
12
|
+
export const transport = {
|
|
13
|
+
makeClient: (target) => new grpc.Client(target, grpc.credentials.createSsl(), {
|
|
14
|
+
"grpc.enable_retries": 0,
|
|
15
|
+
"grpc.max_receive_message_length": policy.receiveBytes,
|
|
16
|
+
"grpc.max_send_message_length": 131072,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
export function connect(client, signal) {
|
|
20
|
+
return connectGrpc(client, signal, policy.connectionTimeout);
|
|
21
|
+
}
|
|
22
|
+
function openStream(client, auth) {
|
|
23
|
+
return client.makeBidiStreamRequest("/rime.TextToSpeech/SynthesizeStreaming", serializer(schema.StreamingSynthesisRequestSchema), deserializer(schema.SynthesisResponseStreamSchema), auth);
|
|
24
|
+
}
|
|
25
|
+
function header(voice, language, timestamps, customLexicon, audioFormat, samplingRate, timeScaleFactor) {
|
|
26
|
+
return create(schema.StreamingSynthesisRequestSchema, {
|
|
27
|
+
payload: {
|
|
28
|
+
case: "header",
|
|
29
|
+
value: create(schema.SynthesisRequestSchema, {
|
|
30
|
+
speaker: voice,
|
|
31
|
+
language,
|
|
32
|
+
splitStrategy: schema.SplitStrategy.ACCUMULATE_SENTENCE,
|
|
33
|
+
audioParameters: { audioFormat, samplingRate, timeScaleFactor },
|
|
34
|
+
timestamps: timestamps ? { enable: true } : undefined,
|
|
35
|
+
customLexicon: [...customLexicon],
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
function textMessage(text) {
|
|
41
|
+
return create(schema.StreamingSynthesisRequestSchema, {
|
|
42
|
+
payload: { case: "textChunk", value: text },
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function unary(prepared, name, input, output, request, signal) {
|
|
46
|
+
let call;
|
|
47
|
+
let id = null;
|
|
48
|
+
try {
|
|
49
|
+
signal.throwIfAborted();
|
|
50
|
+
const operation = new Promise((resolve, reject) => {
|
|
51
|
+
call = prepared.client.makeUnaryRequest("/rime.TextToSpeech/" + name, serializer(input), deserializer(output), request, prepared.metadata, (error, result) => (error ? reject(error) : resolve(result)));
|
|
52
|
+
call.once("metadata", (metadata) => {
|
|
53
|
+
id = requestId(metadata);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
return await abortable(operation, signal);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
call?.cancel();
|
|
60
|
+
if (signal.aborted) {
|
|
61
|
+
if (signal.reason instanceof errors.RimeTimeoutError)
|
|
62
|
+
throw new errors.RimeTimeoutError(signal.reason.message, id, {
|
|
63
|
+
cause: signal.reason.cause,
|
|
64
|
+
});
|
|
65
|
+
throw signal.reason;
|
|
66
|
+
}
|
|
67
|
+
throw nativeError(error, id);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export async function discover(prepared, kind, language, signal) {
|
|
71
|
+
// Exactly one attempt. The client owns retries and the shared deadline.
|
|
72
|
+
if (kind === "voices") {
|
|
73
|
+
const method = schema.TextToSpeech.method.getSupportedSpeakers;
|
|
74
|
+
const response = await unary(prepared, method.name, method.input, method.output, create(method.input, { language: language ?? undefined }), signal);
|
|
75
|
+
return response.speakers;
|
|
76
|
+
}
|
|
77
|
+
const method = schema.TextToSpeech.method.getSupportedLanguages;
|
|
78
|
+
const response = await unary(prepared, method.name, method.input, method.output, create(method.input), signal);
|
|
79
|
+
return response.languages;
|
|
80
|
+
}
|
|
81
|
+
export class SynthesisCall {
|
|
82
|
+
audioFormat = "audio/pcm";
|
|
83
|
+
timestampsRequested = false;
|
|
84
|
+
timestampTrailer = new TimestampTrailer();
|
|
85
|
+
rpc;
|
|
86
|
+
constructor(prepared, signal) {
|
|
87
|
+
this.rpc = new BidiCall(openStream(prepared.client, prepared.metadata), signal);
|
|
88
|
+
}
|
|
89
|
+
get requestId() {
|
|
90
|
+
return this.rpc.requestId;
|
|
91
|
+
}
|
|
92
|
+
get done() {
|
|
93
|
+
return this.rpc.done;
|
|
94
|
+
}
|
|
95
|
+
cancel() {
|
|
96
|
+
this.rpc.cancel();
|
|
97
|
+
}
|
|
98
|
+
start(voice, language, timestamps = false, customLexicon = [], audioFormat = "audio/pcm", sampleRate = 24000, timeScaleFactor) {
|
|
99
|
+
this.timestampsRequested = timestamps;
|
|
100
|
+
this.audioFormat = audioFormat;
|
|
101
|
+
const message = header(voice, language, timestamps, customLexicon, audioFormat, sampleRate, timeScaleFactor);
|
|
102
|
+
return this.rpc.write(message);
|
|
103
|
+
}
|
|
104
|
+
timestampResult() {
|
|
105
|
+
return this.timestampTrailer.result(this.requestId);
|
|
106
|
+
}
|
|
107
|
+
write(text) {
|
|
108
|
+
return this.rpc.write(textMessage(text));
|
|
109
|
+
}
|
|
110
|
+
finishInput() {
|
|
111
|
+
this.rpc.finishInput();
|
|
112
|
+
}
|
|
113
|
+
checkFormat(contentType) {
|
|
114
|
+
if (contentType !== this.audioFormat)
|
|
115
|
+
throw new errors.RimeAudioFormatError(`Expected ${this.audioFormat} from the service`, this.requestId);
|
|
116
|
+
}
|
|
117
|
+
async *audio() {
|
|
118
|
+
const headers = await this.rpc.headers();
|
|
119
|
+
const contentType = headers.get("x-rime-audio-content-type")[0];
|
|
120
|
+
for await (const response of this.rpc.responses()) {
|
|
121
|
+
if (response.payload.case === "trailer" && this.timestampsRequested)
|
|
122
|
+
this.timestampTrailer.accept(response.payload.value);
|
|
123
|
+
if (response.payload.case !== "audio")
|
|
124
|
+
continue;
|
|
125
|
+
if (this.timestampTrailer.seen)
|
|
126
|
+
this.timestampTrailer.invalid = true;
|
|
127
|
+
if (response.payload.value.length)
|
|
128
|
+
this.checkFormat(contentType);
|
|
129
|
+
yield response.payload.value;
|
|
130
|
+
}
|
|
131
|
+
this.checkFormat(contentType);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { RimeInputError } from "./errors.js";
|
|
2
|
+
export function endpointAddress(endpoint) {
|
|
3
|
+
const error = "endpoint must be a hostname with an optional port (1-65535), without a scheme or path";
|
|
4
|
+
if (typeof endpoint !== "string" || /\s/.test(endpoint))
|
|
5
|
+
throw new RimeInputError(error);
|
|
6
|
+
const [host, port, extra] = endpoint.split(":");
|
|
7
|
+
if (!host ||
|
|
8
|
+
host.length > 253 ||
|
|
9
|
+
extra !== undefined ||
|
|
10
|
+
host
|
|
11
|
+
.split(".")
|
|
12
|
+
.some((label) => !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test(label)) ||
|
|
13
|
+
(port !== undefined &&
|
|
14
|
+
(!/^[0-9]{1,5}$/.test(port) || Number(port) < 1 || Number(port) > 65535)))
|
|
15
|
+
throw new RimeInputError(error);
|
|
16
|
+
return {
|
|
17
|
+
target: `${host.toLowerCase()}:${port === undefined ? 443 : Number(port)}`,
|
|
18
|
+
audience: host.toLowerCase(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function timeout(value, inherited = null) {
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
return inherited;
|
|
24
|
+
if (value !== null &&
|
|
25
|
+
(typeof value !== "number" || !Number.isFinite(value) || value <= 0))
|
|
26
|
+
throw new RimeInputError("timeout must be finite positive seconds or null");
|
|
27
|
+
return value;
|
|
28
|
+
}
|
package/docs/realtime.md
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# Realtime / Prism for Node.js
|
|
2
|
+
|
|
3
|
+
The endpoint must use Rime's `prism.` events and `prism_` fields. Endpoints
|
|
4
|
+
that use the former `prsm` names are not supported.
|
|
5
|
+
|
|
6
|
+
Keep one event consumer running while your application sends input, runs tools,
|
|
7
|
+
and plays audio. The SDK manages the protocol state. Your application owns
|
|
8
|
+
microphone capture, tool execution, and playback.
|
|
9
|
+
|
|
10
|
+
Start with the [recorded-speech quick start](../README.md#prism-send-recorded-speech)
|
|
11
|
+
to save a spoken reply without audio hardware.
|
|
12
|
+
|
|
13
|
+
For live conversations, keep the session open after a response is cancelled.
|
|
14
|
+
Report playback from the output clock. Saving a file is not playback.
|
|
15
|
+
Typed turns also support mixed text and voice applications.
|
|
16
|
+
|
|
17
|
+
## Optional conversation runner
|
|
18
|
+
|
|
19
|
+
Use `runConversation` to connect your audio adapters once. It sends input,
|
|
20
|
+
queues output, handles interruption, reports measured playback, and stops its
|
|
21
|
+
operations when the conversation ends. The direct session methods remain available.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { Rime, runConversation } from "@rimelabs/sdk";
|
|
25
|
+
|
|
26
|
+
const client = new Rime();
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
try {
|
|
29
|
+
const session = await client.realtime.connect();
|
|
30
|
+
// microphone and speaker are adapters owned by your application.
|
|
31
|
+
await runConversation(session, {
|
|
32
|
+
source: microphone,
|
|
33
|
+
playback: speaker,
|
|
34
|
+
onEvent(event) {
|
|
35
|
+
if (event.payload.kind === "transcript.final")
|
|
36
|
+
console.log(event.payload.text);
|
|
37
|
+
},
|
|
38
|
+
signal: controller.signal,
|
|
39
|
+
});
|
|
40
|
+
} finally {
|
|
41
|
+
await client.close();
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The input adapter implements `ConversationSource`.
|
|
46
|
+
`read(signal: AbortSignal): Promise<AudioChunk | null>` returns one chunk,
|
|
47
|
+
with an optional format, or `null` at EOF. An omitted format uses 16 kHz mono
|
|
48
|
+
PCM16. Abort must reject and release a pending
|
|
49
|
+
read. The runner awaits each `session.sendAudio` call before reading again. It
|
|
50
|
+
uses the session's existing PCM conversion and transport backpressure. Input
|
|
51
|
+
continues during output playback.
|
|
52
|
+
|
|
53
|
+
The playback adapter implements `ConversationPlayback`:
|
|
54
|
+
|
|
55
|
+
| Method | Contract |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| `open(output: OutputRef): ConversationOutput` | Queue this output after previously opened outputs. It can be opened before the first audio chunk. |
|
|
58
|
+
| `interrupt(outputs: readonly OutputRef[]): void` | Stop the selected outputs as one batch, discard their unplayed audio, then resume remaining output in queue order. |
|
|
59
|
+
| `stop(signal: AbortSignal): Promise<void>` | Confirm all playback has stopped and queued audio is discarded. Keep caller-owned resources open. |
|
|
60
|
+
|
|
61
|
+
Each `ConversationOutput` implements:
|
|
62
|
+
|
|
63
|
+
| Method | Contract |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `write(chunk: AudioChunk): void` | Queue 24 kHz mono PCM16 without blocking. Bound the queue and throw on overflow. |
|
|
66
|
+
| `end(): void` | Accept no more audio. Let queued and device audio drain. |
|
|
67
|
+
| `completion(signal: AbortSignal): Promise<number>` | Return actual played milliseconds after drain or interruption. Reject if measurement fails or the signal aborts. |
|
|
68
|
+
|
|
69
|
+
All synchronous adapter methods must return promptly. Completion can be awaited
|
|
70
|
+
before `end()`. Keep receipts separate for each `OutputRef`, even when a new
|
|
71
|
+
response starts before an old receipt completes. Play outputs in the order they
|
|
72
|
+
were opened, using one FIFO audio queue. Do not mix outputs, interleave their
|
|
73
|
+
audio, or play later outputs before earlier outputs finish or are interrupted.
|
|
74
|
+
Prism assumes that interrupting a partially played reply leaves later replies
|
|
75
|
+
in the same turn unheard. Independent concurrent playback violates that assumption.
|
|
76
|
+
The runner passes all affected output references in one `interrupt` call. Stop
|
|
77
|
+
them and discard their queued audio before starting any remaining output. Then
|
|
78
|
+
resume valid output in queue order, without waiting for another chunk or event.
|
|
79
|
+
Interruption must work after `end()` and tolerate repeated calls. A remote
|
|
80
|
+
adapter must preserve the batch, for example with one message containing all
|
|
81
|
+
selected output IDs. Shutdown stops all output and must not resume playback.
|
|
82
|
+
Receipt delivery can be delayed or arrive out of order; audible playback cannot.
|
|
83
|
+
A receipt includes the device buffer where device timing is available. Bytes
|
|
84
|
+
received or queued do not measure playback. Zero is valid for an output that
|
|
85
|
+
played no audio.
|
|
86
|
+
|
|
87
|
+
Abort must release each pending adapter operation and remove its listeners.
|
|
88
|
+
Cancellation of a completion wait must not cancel the device receipt. The runner
|
|
89
|
+
stops input, event processing, and report submission on exit. It then awaits
|
|
90
|
+
`stop(signal)` before aborting remaining receipt waits. The stop signal is
|
|
91
|
+
independent of caller cancellation and has a five-second deadline. `stop` must
|
|
92
|
+
resolve only after local or remote playback has stopped, including device
|
|
93
|
+
buffers. Sending a stop command is not confirmation. It must reject and release
|
|
94
|
+
its wait when the stop signal aborts. The runner awaits this cancellation cleanup
|
|
95
|
+
before it returns, so cleanup can extend beyond the deadline. A missing confirmation raises
|
|
96
|
+
`RimeTimeoutError`; disconnects and other shutdown failures must reject too.
|
|
97
|
+
The caller opens and closes the adapter resources. The runner does not open
|
|
98
|
+
audio devices or host a browser.
|
|
99
|
+
|
|
100
|
+
`onEvent` is optional. It receives every event read from `session.events`, in
|
|
101
|
+
order, after the runner applies audio coordination. It must be synchronous and
|
|
102
|
+
nonblocking. Returning a promise is rejected. Callback exceptions reach the
|
|
103
|
+
caller. The callback can schedule application work. Continue to use
|
|
104
|
+
`submitToolResult` and `continueReply`; the runner neither registers nor executes
|
|
105
|
+
tools. The application must cancel and await its own tool tasks. Response
|
|
106
|
+
failures and nonterminal faults are forwarded for application handling. Session
|
|
107
|
+
failures reject the runner promise.
|
|
108
|
+
|
|
109
|
+
The runner is the only event reader. Do not consume `session.events` at the same
|
|
110
|
+
time, call `reportPlayback` independently, or send a second audio source. Other
|
|
111
|
+
session methods remain available for manual tools and application actions.
|
|
112
|
+
|
|
113
|
+
On confirmed `speech.started`, the runner uses the effective
|
|
114
|
+
`session.info.interruptOnSpeech` setting. It stops affected output, waits for the
|
|
115
|
+
interrupted receipt, and reports the measured position. It ignores late audio
|
|
116
|
+
for that response. A `speech.hint` alone does not stop playback. A server
|
|
117
|
+
`truncate_not_current` refusal is forwarded as an event; it does not stop a newer
|
|
118
|
+
response. Finished reports wait for generation completion and every output
|
|
119
|
+
receipt, including outstanding playback from earlier responses. Prism treats
|
|
120
|
+
the latest drain token as confirmation that all earlier playback has stopped.
|
|
121
|
+
An abandoned response also stops its dependent tool continuations, identified
|
|
122
|
+
by response parent references. Unrelated later turns remain eligible to play.
|
|
123
|
+
Late audio from an interrupted response or its continuations is discarded.
|
|
124
|
+
The runner retains at most 128 response parent references.
|
|
125
|
+
A report retains the measured duration of its own response. Waiting for earlier
|
|
126
|
+
playback does not block events or input, and does not wait for later responses.
|
|
127
|
+
|
|
128
|
+
Once called, the runner owns the session's active lifetime. Input EOF, explicit
|
|
129
|
+
session closure, abort, or failure stops all runner operations and closes the
|
|
130
|
+
session. EOF stops remaining playback and waits for shutdown confirmation.
|
|
131
|
+
Abort, including an abort received during cleanup, waits for playback shutdown
|
|
132
|
+
confirmation or its five-second deadline, then awaits adapter cancellation cleanup
|
|
133
|
+
before rejecting with `signal.reason`.
|
|
134
|
+
Explicit session closure ends normally even with playback reports pending;
|
|
135
|
+
remote connection failures reject with the session error.
|
|
136
|
+
Expected cancellation errors during cleanup are ignored. Other cleanup errors
|
|
137
|
+
reach the caller if there is no original failure. They do not replace an
|
|
138
|
+
existing failure or caller abort. The signal is optional. Use
|
|
139
|
+
an application deadline if an adapter can wait indefinitely.
|
|
140
|
+
|
|
141
|
+
The runner adds no input or callback queue. It retains at most 64 responses,
|
|
142
|
+
128 output streams, and 256 pending operations. Exceeding a limit throws
|
|
143
|
+
`RimeResourceLimitError`. The adapters must also bound their audio queues.
|
|
144
|
+
|
|
145
|
+
The [live voice example](https://github.com/rimelabs/rime-sdk/blob/main/typescript/examples/realtime/conversation.ts) supplies the
|
|
146
|
+
[browser audio adapter](https://github.com/rimelabs/rime-sdk/blob/main/typescript/examples/common/browser-audio.ts). Browser hosting
|
|
147
|
+
stays in the example. Its player uses `AudioContext.getOutputTimestamp()` for
|
|
148
|
+
receipts and shutdown confirmation. A disconnect cannot confirm a remote stop.
|
|
149
|
+
The [manual tool example](https://github.com/rimelabs/rime-sdk/blob/main/typescript/examples/realtime/manual-tools.ts) keeps
|
|
150
|
+
tool execution outside the runner.
|
|
151
|
+
|
|
152
|
+
## Connect
|
|
153
|
+
|
|
154
|
+
Use `await client.realtime.connect()` with `RIME_API_KEY` set. It opens a WebSocket,
|
|
155
|
+
initializes one conversation, and returns a `RealtimeSession`. Settings apply
|
|
156
|
+
once. Open a new session to change them.
|
|
157
|
+
|
|
158
|
+
| Option | Required? | Default and behavior |
|
|
159
|
+
| --- | --- | --- |
|
|
160
|
+
| `endpoint` | No | Defaults to `wss://prism.api.rime.ai/v1/realtime`. Overrides must be full URLs; plain `ws://` is allowed only for localhost. |
|
|
161
|
+
| `voice` | If the server has no default voice | Omitted or `null` uses the server default. Supplied strings must be nonblank. |
|
|
162
|
+
| `instructions` | No | Omitted or `null` uses server defaults. Supplied strings must be nonblank. |
|
|
163
|
+
| `tools` | No | `[]`. Each tool requires `name` and a JSON Schema `parameters` object. `description` defaults to `""`. |
|
|
164
|
+
| `interruptOnSpeech` | No | `true`. Requests interruption when the server confirms user speech. |
|
|
165
|
+
| `turnDetection` | No | Omitted settings use the server's turn timing defaults. |
|
|
166
|
+
| `timeouts` | No | `{ connectS: 10, readyS: 30, requestS: 10 }`. Each field is optional. Units are seconds. |
|
|
167
|
+
| `model` | No | `"prism"`, the only accepted value. |
|
|
168
|
+
| `signal` | No | An `AbortSignal` that cancels connection setup. It does not cancel an established session. |
|
|
169
|
+
|
|
170
|
+
Credentials come from `new Rime({ apiKey: "..." })` or `RIME_API_KEY`. Prism uses
|
|
171
|
+
`Authorization: Bearer <key>`, without a TTS token exchange. TTS endpoint, model,
|
|
172
|
+
voice, and timeout settings do not apply to Realtime.
|
|
173
|
+
|
|
174
|
+
After connection, `session.info` contains `sessionId`, `model`, `voice`,
|
|
175
|
+
`interruptOnSpeech`, `turnDetection`, `toolResultTimeoutS`, and
|
|
176
|
+
`toolContinuationTimeoutS`.
|
|
177
|
+
Check the effective interruption setting there. Tool wait durations are total
|
|
178
|
+
server deadlines, not remaining time. A `null` duration can mean an omitted
|
|
179
|
+
field; it does not prove that no deadline exists.
|
|
180
|
+
|
|
181
|
+
Close the session in `finally` with `await session.close()`. Closing the parent
|
|
182
|
+
client closes all its TTS and Realtime operations. Both objects also support
|
|
183
|
+
`Symbol.asyncDispose`. Repeated close calls are safe.
|
|
184
|
+
|
|
185
|
+
## Optional turn timing
|
|
186
|
+
|
|
187
|
+
Pass a `TurnDetection` object to override how readily Prism ends a spoken turn.
|
|
188
|
+
Each field is optional; omitted fields use server defaults.
|
|
189
|
+
|
|
190
|
+
| Field | Values | Current server default |
|
|
191
|
+
| --- | --- | --- |
|
|
192
|
+
| `eagerness` | `"low"`, `"medium"`, `"high"`, `"auto"` | `"auto"`, equivalent to `"medium"` |
|
|
193
|
+
| `endOfTurnTimeoutMs` | Integer from 1,000 through 10,000 | 5,000 ms |
|
|
194
|
+
|
|
195
|
+
`"low"` waits for more certainty that the user has finished. `"high"` answers
|
|
196
|
+
sooner, with more risk of cutting in. The timeout is a maximum silence limit,
|
|
197
|
+
not a fixed delay before each reply. If the server falls back to its local
|
|
198
|
+
voice activity detector, it ends turns after 500 ms of silence instead.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const session = await client.realtime.connect({
|
|
202
|
+
turnDetection: { eagerness: "low", endOfTurnTimeoutMs: 5000 },
|
|
203
|
+
});
|
|
204
|
+
try {
|
|
205
|
+
console.log(session.info.turnDetection);
|
|
206
|
+
// Send input and consume session.events here.
|
|
207
|
+
} finally {
|
|
208
|
+
await session.close();
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`session.info.turnDetection` is a `TurnDetectionInfo` with the settings the
|
|
213
|
+
server reported in its initialization acknowledgment. A missing field is
|
|
214
|
+
`null`; the SDK does not substitute a requested value or assume a server
|
|
215
|
+
default. An unrecognized server eagerness becomes `"unknown"`.
|
|
216
|
+
`interruptOnSpeech` remains a separate connection option.
|
|
217
|
+
|
|
218
|
+
## Choose an operation
|
|
219
|
+
|
|
220
|
+
Every method returns a promise. Arguments without `?` are required.
|
|
221
|
+
|
|
222
|
+
| Method | Result and behavior |
|
|
223
|
+
| --- | --- |
|
|
224
|
+
| `sendText(text, options?)` | Starts a typed user turn after readiness; returns `ResponseRef` at response creation. |
|
|
225
|
+
| `sendAudio(chunk, options?)` | Sends microphone audio with transport backpressure. |
|
|
226
|
+
| `requestReply(options?)` | Requests a reply without new user input; returns `ResponseRef`. Options can include `instruction` and `toolCall`. |
|
|
227
|
+
| `addMessage(role, text, options?)` | Adds user or assistant history without a reply; returns `ItemRef` after acknowledgment. |
|
|
228
|
+
| `submitToolResult(call, output, options?)` | Records a string result and waits for acknowledgment. |
|
|
229
|
+
| `continueReply(parent, options?)` | Waits for parent completion and all tool result acknowledgments, then returns a new `ResponseRef`. |
|
|
230
|
+
| `cancel(response, options?)` | Requests generation cancellation and waits for a terminal response or refusal. |
|
|
231
|
+
| `clearAudio(options?)` | Clears buffered input and resets audio conversion after acknowledgment. |
|
|
232
|
+
| `reportPlayback(report, options?)` | Reports interrupted or finished playback. |
|
|
233
|
+
| `close()` | Closes the session and releases its resources. |
|
|
234
|
+
|
|
235
|
+
Operation options accept `signal?: AbortSignal`. `requestReply` accepts its
|
|
236
|
+
instruction, tool call, and signal in one object. Methods other than `sendText`,
|
|
237
|
+
`requestReply`, `addMessage`, and `continueReply` return `Promise<void>`.
|
|
238
|
+
|
|
239
|
+
Text must be nonblank and contain at most 4,000 Unicode characters. History
|
|
240
|
+
roles are `"user"` and `"assistant"`. A reply instruction applies to one response;
|
|
241
|
+
it does not replace session instructions.
|
|
242
|
+
|
|
243
|
+
A returned `ResponseRef` means generation started. Read `response.ended` for the
|
|
244
|
+
terminal status. Generation can finish before cancellation takes effect, so a
|
|
245
|
+
successful `cancel()` does not guarantee a `"cancelled"` status.
|
|
246
|
+
|
|
247
|
+
## Send audio
|
|
248
|
+
|
|
249
|
+
Pass a plain object with `data: Uint8Array`. Node.js `Buffer` is also accepted.
|
|
250
|
+
The optional `format` object defaults to 16 kHz mono signed little-endian PCM16.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
await session.sendAudio({
|
|
254
|
+
data: pcmBytes,
|
|
255
|
+
format: { sampleRate: 48000, channels: 2 },
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Supported `sampleRate` values are `8000`, `16000`, `24000`, and `48000`.
|
|
260
|
+
Channels can be `1` or `2`; `encoding` can only be `"pcm_s16le"`. Each field in
|
|
261
|
+
`format` is optional. Chunks must contain complete sample frames and at most
|
|
262
|
+
192,000 bytes. The SDK downmixes and resamples to 16 kHz mono, retaining state
|
|
263
|
+
across calls. A format change or acknowledged `clearAudio()` resets that state.
|
|
264
|
+
|
|
265
|
+
Await each send and bound your capture queue. Keep audio flowing, including
|
|
266
|
+
silence and while the assistant speaks or tools run. Prism detects and commits
|
|
267
|
+
speech; there is no manual commit method. Echo cancellation belongs to your app.
|
|
268
|
+
|
|
269
|
+
Received `audio.delta` events carry 24 kHz mono PCM16. The outgoing chunk limit
|
|
270
|
+
does not apply to received audio.
|
|
271
|
+
|
|
272
|
+
## Handle tools
|
|
273
|
+
|
|
274
|
+
Declare tools with plain `ToolDefinition` objects. Names must be nonblank and
|
|
275
|
+
unique; schemas must contain finite JSON values. The SDK copies schemas before
|
|
276
|
+
connection setup, so later caller changes cannot alter initialization.
|
|
277
|
+
|
|
278
|
+
Set `prismFiller` to control what Prism says while the tool runs:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
import type { ToolDefinition } from "@rimelabs/sdk";
|
|
282
|
+
|
|
283
|
+
const pressDigits: ToolDefinition = {
|
|
284
|
+
name: "press_digits",
|
|
285
|
+
parameters: { type: "object", properties: { digits: { type: "string" } } },
|
|
286
|
+
prismFiller: { type: "none" },
|
|
287
|
+
};
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
| Setting | Behavior |
|
|
291
|
+
| --- | --- |
|
|
292
|
+
| Omitted | Use the server default, currently `auto`. |
|
|
293
|
+
| `{ type: "none" }` | Suppress filler for this tool. |
|
|
294
|
+
| `{ type: "text", text: "One moment." }` | Speak the supplied text. |
|
|
295
|
+
| `{ type: "auto" }` | Let Prism write a line from the tool description. |
|
|
296
|
+
| `{ type: "auto", instructions: "Say you are checking the order." }` | Guide the generated line. |
|
|
297
|
+
|
|
298
|
+
Text and instructions must be valid UTF-8, contain at most 500 Unicode scalar
|
|
299
|
+
values, and contain a character outside the Unicode `White_Space` set. This
|
|
300
|
+
matches the engine's validation. The SDK preserves the supplied text and copies
|
|
301
|
+
filler settings before connection setup. Invalid settings raise `RimeInputError` before opening a
|
|
302
|
+
connection, including for JavaScript callers. The exported `PrismFiller` union
|
|
303
|
+
also checks TypeScript callers.
|
|
304
|
+
|
|
305
|
+
Filler applies when the reply has said nothing before calling a tool. If a reply
|
|
306
|
+
calls several tools, the first call's tool controls the filler. `none` suppresses
|
|
307
|
+
filler only; it does not silence other speech. Settings are fixed for the session.
|
|
308
|
+
|
|
309
|
+
Execute a tool once for each `tool.call` event. Validate its parsed `arguments`,
|
|
310
|
+
then submit a string result with `submitToolResult(payload.call, output)`.
|
|
311
|
+
Use `JSON.stringify(...)` for a structured result. Call
|
|
312
|
+
`continueReply(payload.call)` when the assistant should answer; a tool call
|
|
313
|
+
reference also carries its parent response identity.
|
|
314
|
+
|
|
315
|
+
Keep the event consumer running while submitting tool results and requesting
|
|
316
|
+
continuation. Run slow tools asynchronously or in a worker so they do not block
|
|
317
|
+
the event loop.
|
|
318
|
+
|
|
319
|
+
A new user turn or proactive reply supersedes the old tool round. Late results
|
|
320
|
+
can still be recorded. Use `requestReply({ toolCall: call })` to ask the assistant
|
|
321
|
+
to report a recorded result. Repeating the same result is safe within the live
|
|
322
|
+
session; changing it is rejected. Do not rerun a tool because delivery failed.
|
|
323
|
+
|
|
324
|
+
## Read events and references
|
|
325
|
+
|
|
326
|
+
Use `for await (const event of session.events)`. Each `SessionEvent` has
|
|
327
|
+
`sessionId`, `eventId`, nullable `requestId`, and a `payload`. Switch on
|
|
328
|
+
`payload.kind` to narrow the TypeScript union. All public types and error classes
|
|
329
|
+
are exported from `@rimelabs/sdk`.
|
|
330
|
+
|
|
331
|
+
| Kind | Payload fields |
|
|
332
|
+
| --- | --- |
|
|
333
|
+
| `input.ready` | No data fields. Turn methods handle the readiness wait. |
|
|
334
|
+
| `speech.hint` | `audioStartMs`; the user may have started speaking over a playing reply. It can be a false alarm: lower playback, do not stop it. |
|
|
335
|
+
| `speech.started` | `itemId`, `audioStartMs` |
|
|
336
|
+
| `speech.stopped` | `itemId`, `audioEndMs`, `endReason`: what ended the turn (`semantic`, `timeout`, `silence`, `max_duration` or `detector_changed`), `unknown` for a newer value, or null from a server that does not send it |
|
|
337
|
+
| `input.committed` | `itemId` |
|
|
338
|
+
| `transcript.delta`, `transcript.final` | `itemId` and `delta` or `text` |
|
|
339
|
+
| `transcript.failed` | `itemId`, `error` |
|
|
340
|
+
| `response.started` | `response`, `cause`, nullable `parent` and `inputItemId` |
|
|
341
|
+
| `message.started` | `output` |
|
|
342
|
+
| `text.delta`, `text.done` | `output` and `delta` or `text` |
|
|
343
|
+
| `audio.delta`, `audio.done` | `output`; a delta also has `audio.data` and a complete `audio.format` |
|
|
344
|
+
| `tool.call` | `call`, `itemId`, `name`, parsed `arguments` |
|
|
345
|
+
| `response.ended` | `response`, `status`, nullable `reason` |
|
|
346
|
+
| `response.abandoned` | `response`; the SDK requests cancellation after the requesting caller aborted. |
|
|
347
|
+
| `error` | `error`, a `RealtimeFault` |
|
|
348
|
+
|
|
349
|
+
References come from the SDK. Pass them back to identify the object you mean.
|
|
350
|
+
|
|
351
|
+
| Reference | Fields |
|
|
352
|
+
| --- | --- |
|
|
353
|
+
| `ResponseRef` | `sessionId`, `responseId` |
|
|
354
|
+
| `ItemRef` | `sessionId`, `itemId` |
|
|
355
|
+
| `ToolCallRef` | `sessionId`, `responseId`, `callId` |
|
|
356
|
+
| `OutputRef` | `response`, `itemId`, `outputIndex`, `contentIndex` |
|
|
357
|
+
|
|
358
|
+
Every reference belongs to one session. Only the last 128 responses are retained;
|
|
359
|
+
older references expire. Current output uses `contentIndex: 0`.
|
|
360
|
+
|
|
361
|
+
Unknown event kinds and extra fields are ignored. Unknown response causes,
|
|
362
|
+
terminal statuses, and error scopes become `"unknown"`. An absent response cause
|
|
363
|
+
means speech. The local `response.abandoned` event has an empty `eventId` and
|
|
364
|
+
`requestId: null`; it can follow `response.ended` when completion races cancellation.
|
|
365
|
+
|
|
366
|
+
## Report playback
|
|
367
|
+
|
|
368
|
+
Generation completion and playback completion are separate. Report the player's
|
|
369
|
+
actual state, including output latency and queued audio.
|
|
370
|
+
|
|
371
|
+
| Report object | Required fields | Optional fields |
|
|
372
|
+
| --- | --- | --- |
|
|
373
|
+
| Interrupted | `kind: "interrupted"`, `output`, `audioEndMs` | None |
|
|
374
|
+
| Finished | `kind: "finished"`, `response` | `playedMs`, `audibleTailMs`; both default to `null` |
|
|
375
|
+
|
|
376
|
+
Pass the object to `await session.reportPlayback(report)`. Interruption records
|
|
377
|
+
the point where caller speech stopped an assistant message. `audioEndMs` must
|
|
378
|
+
be an integer from 0 through 4,294,967,295. Finished playback means all queued
|
|
379
|
+
audio has played or has permanently stopped. Optional durations must be finite,
|
|
380
|
+
nonnegative milliseconds. The SDK waits for generation completion and supplies
|
|
381
|
+
the server's drain token.
|
|
382
|
+
|
|
383
|
+
Saving audio does not establish playback. Reports are optional. If you cannot
|
|
384
|
+
measure playback, omit them and let the server use its estimate. The recorded and typed examples have no player and omit these reports.
|
|
385
|
+
The voice examples report actual playback.
|
|
386
|
+
|
|
387
|
+
## Timeouts and failures
|
|
388
|
+
|
|
389
|
+
Configure positive, finite seconds through `timeouts`. `connectS` bounds the
|
|
390
|
+
WebSocket handshake and initial session event. `readyS` bounds turn admission,
|
|
391
|
+
tool readiness, and waiting for generation to end before a playback report.
|
|
392
|
+
Once a request is submitted, `requestS` bounds its write and acknowledgment.
|
|
393
|
+
|
|
394
|
+
These limits do not bound the entire conversation or generation after creation.
|
|
395
|
+
Set an application deadline and close the session when it expires. The recorded and typed examples use a 60-second deadline. Voice conversations
|
|
396
|
+
run until stopped or the connection fails.
|
|
397
|
+
|
|
398
|
+
`RealtimeAdmissionTimeout` means readiness expired with no request outstanding;
|
|
399
|
+
the session remains usable. An unknown request outcome closes the connection and
|
|
400
|
+
is never replayed. The SDK retries typed-turn admission only after named refusals
|
|
401
|
+
that confirm the request made no change.
|
|
402
|
+
|
|
403
|
+
Protocol faults appear as `error` events and can also reject an affected method
|
|
404
|
+
with `RimeRealtimeError`. Its `.fault` has `code`, `message`, `scope`, nullable
|
|
405
|
+
`requestId`, `responseId`, `itemId`, `correlationId`, `parameter`, and a `callIds`
|
|
406
|
+
array. A response fault does not itself end generation. Session faults and lost
|
|
407
|
+
connections end the session.
|
|
408
|
+
|
|
409
|
+
Malformed fields in known events and mismatched request acknowledgments close
|
|
410
|
+
the session with `RimeStreamError`. Unknown event kinds and extra fields remain
|
|
411
|
+
accepted and ignored. The SDK does not replay a request after a protocol failure.
|
|
412
|
+
|
|
413
|
+
Aborting an operation rejects it with `RimeCancelledError`. For reply requests,
|
|
414
|
+
the SDK prevents an unsent request or cancels a late accepted response. Control
|
|
415
|
+
operations can wait for acknowledgment before rejecting, to keep acknowledgment
|
|
416
|
+
matching correct. A submitted tool result keeps matching its acknowledgment even
|
|
417
|
+
if its caller aborts. Aborting audio after submission starts closes the session;
|
|
418
|
+
before submission, it restores conversion state. Do not resend a chunk with an
|
|
419
|
+
unknown outcome.
|
|
420
|
+
|
|
421
|
+
Keep up with the event stream. The queue holds 256 events; incoming messages
|
|
422
|
+
are limited to 1 MiB. A slow consumer ends the session. There is no automatic
|
|
423
|
+
reconnect, history replay, or live configuration update.
|
|
424
|
+
|
|
425
|
+
Local peer tests cover the SDK protocol handling. Validate the gateway handshake
|
|
426
|
+
and endpoint behavior for the deployment you use.
|