@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
@@ -0,0 +1,16 @@
1
+ import { RimeError } from "../errors.js";
2
+ /** Private transport. The public declarations do not require ws types. */
3
+ export declare class RealtimeConnection {
4
+ private readonly socket;
5
+ private readonly opened;
6
+ private readonly ended;
7
+ private closing;
8
+ private connected;
9
+ readonly ready: Promise<void>;
10
+ onMessage: (raw: string) => void;
11
+ onFailure: (error: RimeError) => void;
12
+ constructor(endpoint: string, authorization: string);
13
+ private failure;
14
+ send(encoded: string): Promise<void>;
15
+ close(): Promise<void>;
16
+ }
@@ -0,0 +1,83 @@
1
+ import WebSocket from "ws";
2
+ import { RimeAuthenticationError, RimeStreamError, RimeUnavailableError, } from "../errors.js";
3
+ import { Deferred } from "./async.js";
4
+ /** Private transport. The public declarations do not require ws types. */
5
+ export class RealtimeConnection {
6
+ socket;
7
+ opened = new Deferred();
8
+ ended = new Deferred();
9
+ closing = null;
10
+ connected = false;
11
+ ready = this.opened.promise;
12
+ onMessage = () => { };
13
+ onFailure = () => { };
14
+ constructor(endpoint, authorization) {
15
+ this.socket = new WebSocket(endpoint, {
16
+ headers: { Authorization: authorization },
17
+ followRedirects: false,
18
+ perMessageDeflate: false,
19
+ maxPayload: 1024 * 1024,
20
+ });
21
+ this.socket.on("open", () => {
22
+ this.connected = true;
23
+ this.opened.resolve();
24
+ });
25
+ this.socket.on("unexpected-response", (request, response) => {
26
+ const error = [401, 403].includes(response.statusCode ?? 0)
27
+ ? new RimeAuthenticationError("Realtime endpoint rejected credentials")
28
+ : new RimeUnavailableError("Realtime endpoint rejected the connection");
29
+ this.failure(error);
30
+ response.destroy();
31
+ request.destroy();
32
+ this.socket.terminate();
33
+ });
34
+ this.socket.on("error", () => this.failure(!this.connected
35
+ ? new RimeUnavailableError("Realtime handshake failed")
36
+ : new RimeStreamError("Invalid event or lost realtime connection")));
37
+ this.socket.on("message", (data, binary) => {
38
+ if (binary) {
39
+ this.failure(new RimeStreamError("Expected a JSON text event"));
40
+ return;
41
+ }
42
+ this.onMessage(data.toString());
43
+ });
44
+ this.socket.on("close", () => {
45
+ this.ended.resolve();
46
+ this.failure(new RimeStreamError("Realtime connection closed; reconnect is not automatic"));
47
+ });
48
+ }
49
+ failure(error) {
50
+ this.opened.reject(error);
51
+ this.onFailure(error);
52
+ }
53
+ send(encoded) {
54
+ return new Promise((resolve, reject) => {
55
+ this.socket.send(encoded, (error) => {
56
+ if (error)
57
+ reject(new RimeStreamError("Realtime write failed; outcome unknown"));
58
+ else
59
+ resolve();
60
+ });
61
+ });
62
+ }
63
+ close() {
64
+ if (!this.closing) {
65
+ this.closing = (async () => {
66
+ if (this.socket.readyState === WebSocket.CLOSED)
67
+ return;
68
+ const timer = setTimeout(() => this.socket.terminate(), 2000);
69
+ try {
70
+ if (this.socket.readyState === WebSocket.CONNECTING)
71
+ this.socket.terminate();
72
+ else
73
+ this.socket.close();
74
+ await this.ended.promise;
75
+ }
76
+ finally {
77
+ clearTimeout(timer);
78
+ }
79
+ })();
80
+ }
81
+ return this.closing;
82
+ }
83
+ }
@@ -0,0 +1,240 @@
1
+ import type { PCMFormat } from "../pcm.js";
2
+ export type { PCMFormat } from "../pcm.js";
3
+ import { RimeError, RimeTimeoutError } from "../errors.js";
4
+ export type JsonValue = null | boolean | number | string | JsonValue[] | JsonObject;
5
+ export interface JsonObject {
6
+ [key: string]: JsonValue;
7
+ }
8
+ /** All durations are positive, finite seconds. */
9
+ export interface RealtimeTimeouts {
10
+ readonly connectS?: number;
11
+ readonly readyS?: number;
12
+ readonly requestS?: number;
13
+ }
14
+ export interface RealtimeOperationOptions {
15
+ readonly signal?: AbortSignal;
16
+ }
17
+ /** Speech while a tool runs. Text and instructions must be nonblank and at
18
+ * most 500 Unicode scalar values. Invalid UTF-8 is rejected.
19
+ * Omit the setting to use the server default. */
20
+ export type PrismFiller = {
21
+ readonly type: "none";
22
+ readonly text?: never;
23
+ readonly instructions?: never;
24
+ } | {
25
+ readonly type: "text";
26
+ readonly text: string;
27
+ readonly instructions?: never;
28
+ } | {
29
+ readonly type: "auto";
30
+ readonly instructions?: string;
31
+ readonly text?: never;
32
+ };
33
+ export interface ToolDefinition {
34
+ readonly name: string;
35
+ readonly parameters: JsonObject;
36
+ readonly description?: string;
37
+ readonly prismFiller?: PrismFiller;
38
+ }
39
+ export type TurnEagerness = "low" | "medium" | "high" | "auto";
40
+ /** Omitted fields use the server's turn timing defaults. */
41
+ export interface TurnDetection {
42
+ /** How readily Prism decides that the user has finished speaking. */
43
+ readonly eagerness?: TurnEagerness;
44
+ /** Maximum silence before ending a turn: an integer from 1000 to 10000 ms. */
45
+ readonly endOfTurnTimeoutMs?: number;
46
+ }
47
+ /** Effective server settings. Null means the server did not publish the field. */
48
+ export interface TurnDetectionInfo {
49
+ readonly eagerness: TurnEagerness | "unknown" | null;
50
+ readonly endOfTurnTimeoutMs: number | null;
51
+ }
52
+ export interface RealtimeConnectOptions extends RealtimeOperationOptions {
53
+ /** Defaults to wss://prism.api.rime.ai/v1/realtime. */
54
+ readonly endpoint?: string;
55
+ readonly model?: "prism";
56
+ readonly voice?: string | null;
57
+ readonly instructions?: string | null;
58
+ readonly tools?: readonly ToolDefinition[];
59
+ readonly interruptOnSpeech?: boolean;
60
+ readonly turnDetection?: TurnDetection;
61
+ readonly timeouts?: RealtimeTimeouts;
62
+ }
63
+ export interface ReplyOptions extends RealtimeOperationOptions {
64
+ readonly instruction?: string | null;
65
+ readonly toolCall?: ToolCallRef | null;
66
+ }
67
+ export interface AudioChunk {
68
+ readonly data: Uint8Array;
69
+ readonly format?: PCMFormat;
70
+ }
71
+ export interface ResponseRef {
72
+ readonly sessionId: string;
73
+ readonly responseId: string;
74
+ }
75
+ export interface ItemRef {
76
+ readonly sessionId: string;
77
+ readonly itemId: string;
78
+ }
79
+ export interface ToolCallRef extends ResponseRef {
80
+ readonly callId: string;
81
+ }
82
+ export interface OutputRef {
83
+ readonly response: ResponseRef;
84
+ readonly itemId: string;
85
+ readonly outputIndex: number;
86
+ readonly contentIndex: number;
87
+ }
88
+ export interface SessionInfo {
89
+ readonly sessionId: string;
90
+ readonly model: "prism";
91
+ readonly voice: string;
92
+ readonly interruptOnSpeech: boolean;
93
+ readonly toolResultTimeoutS: number | null;
94
+ readonly toolContinuationTimeoutS: number | null;
95
+ readonly turnDetection: TurnDetectionInfo;
96
+ }
97
+ export interface RealtimeFault {
98
+ readonly code: string;
99
+ readonly message: string;
100
+ readonly scope: "event" | "utterance" | "response" | "tool_roundtrip" | "session" | "unknown";
101
+ readonly requestId: string | null;
102
+ readonly responseId: string | null;
103
+ readonly itemId: string | null;
104
+ readonly callIds: readonly string[];
105
+ readonly correlationId: string | null;
106
+ readonly parameter: string | null;
107
+ }
108
+ export declare class RealtimeAdmissionTimeout extends RimeTimeoutError {
109
+ }
110
+ export declare class RimeRealtimeError extends RimeError {
111
+ readonly fault: RealtimeFault;
112
+ constructor(fault: RealtimeFault);
113
+ }
114
+ export interface InputReady {
115
+ readonly kind: "input.ready";
116
+ }
117
+ /** The user may have started speaking over a playing reply. Prism sends it
118
+ * before `speech.started` when it may interrupt the reply; it can be a false
119
+ * alarm, so lower playback on it rather than stopping. */
120
+ export interface SpeechHint {
121
+ readonly kind: "speech.hint";
122
+ readonly audioStartMs: number;
123
+ }
124
+ export interface SpeechStarted {
125
+ readonly kind: "speech.started";
126
+ readonly itemId: string;
127
+ readonly audioStartMs: number;
128
+ }
129
+ /** What ended the user's turn:
130
+ * - `semantic`: Prism judged from the user's speech that they had finished;
131
+ * `eagerness` sets how readily it does.
132
+ * - `timeout`: the user was silent for `endOfTurnTimeoutMs`, and the timeout
133
+ * ended the turn before Prism judged that they had finished.
134
+ * - `silence`: Prism's backup turn detection ended the turn at a short pause;
135
+ * it does not apply `eagerness` or `endOfTurnTimeoutMs`.
136
+ * - `max_duration`: the user spoke for the longest turn Prism takes; what
137
+ * they say next starts a new turn.
138
+ * - `detector_changed`: Prism switched between its main and backup turn
139
+ * detection while the user spoke; nothing the user did ended the turn. */
140
+ export type SpeechEndReason = "semantic" | "timeout" | "silence" | "max_duration" | "detector_changed";
141
+ export interface SpeechStopped {
142
+ readonly kind: "speech.stopped";
143
+ readonly itemId: string;
144
+ readonly audioEndMs: number;
145
+ /** Null means the server did not publish the field. */
146
+ readonly endReason: SpeechEndReason | "unknown" | null;
147
+ }
148
+ export interface InputCommitted {
149
+ readonly kind: "input.committed";
150
+ readonly itemId: string;
151
+ }
152
+ export interface TranscriptDelta {
153
+ readonly kind: "transcript.delta";
154
+ readonly itemId: string;
155
+ readonly delta: string;
156
+ }
157
+ export interface TranscriptFinal {
158
+ readonly kind: "transcript.final";
159
+ readonly itemId: string;
160
+ readonly text: string;
161
+ }
162
+ export interface TranscriptFailed {
163
+ readonly kind: "transcript.failed";
164
+ readonly itemId: string;
165
+ readonly error: RealtimeFault;
166
+ }
167
+ export interface ResponseStarted {
168
+ readonly kind: "response.started";
169
+ readonly response: ResponseRef;
170
+ readonly cause: "speech" | "text" | "proactive" | "tools" | "unknown";
171
+ readonly parent: ResponseRef | null;
172
+ readonly inputItemId: string | null;
173
+ }
174
+ export interface MessageStarted {
175
+ readonly kind: "message.started";
176
+ readonly output: OutputRef;
177
+ }
178
+ export interface TextDelta {
179
+ readonly kind: "text.delta";
180
+ readonly output: OutputRef;
181
+ readonly delta: string;
182
+ }
183
+ export interface TextDone {
184
+ readonly kind: "text.done";
185
+ readonly output: OutputRef;
186
+ readonly text: string;
187
+ }
188
+ export interface AudioDelta {
189
+ readonly kind: "audio.delta";
190
+ readonly output: OutputRef;
191
+ readonly audio: {
192
+ readonly data: Uint8Array;
193
+ readonly format: Required<PCMFormat>;
194
+ };
195
+ }
196
+ export interface AudioDone {
197
+ readonly kind: "audio.done";
198
+ readonly output: OutputRef;
199
+ }
200
+ export interface ToolCall {
201
+ readonly kind: "tool.call";
202
+ readonly call: ToolCallRef;
203
+ readonly itemId: string;
204
+ readonly name: string;
205
+ readonly arguments: JsonObject;
206
+ }
207
+ export interface ResponseAbandoned {
208
+ readonly kind: "response.abandoned";
209
+ readonly response: ResponseRef;
210
+ }
211
+ export interface ResponseEnded {
212
+ readonly kind: "response.ended";
213
+ readonly response: ResponseRef;
214
+ readonly status: "completed" | "cancelled" | "failed" | "unknown";
215
+ readonly reason: string | null;
216
+ }
217
+ export interface FaultEvent {
218
+ readonly kind: "error";
219
+ readonly error: RealtimeFault;
220
+ }
221
+ export type InputEvent = InputReady | SpeechHint | SpeechStarted | SpeechStopped | InputCommitted | TranscriptDelta | TranscriptFinal | TranscriptFailed;
222
+ export type ResponseEvent = ResponseStarted | ResponseAbandoned | MessageStarted | TextDelta | TextDone | AudioDelta | AudioDone | ToolCall | ResponseEnded;
223
+ export interface SessionEvent {
224
+ readonly sessionId: string;
225
+ readonly eventId: string;
226
+ readonly requestId: string | null;
227
+ readonly payload: InputEvent | ResponseEvent | FaultEvent;
228
+ }
229
+ export interface PlaybackInterrupted {
230
+ readonly kind: "interrupted";
231
+ readonly output: OutputRef;
232
+ readonly audioEndMs: number;
233
+ }
234
+ export interface PlaybackFinished {
235
+ readonly kind: "finished";
236
+ readonly response: ResponseRef;
237
+ readonly playedMs?: number | null;
238
+ readonly audibleTailMs?: number | null;
239
+ }
240
+ export type PlaybackReport = PlaybackInterrupted | PlaybackFinished;
@@ -0,0 +1,10 @@
1
+ import { RimeError, RimeTimeoutError } from "../errors.js";
2
+ export class RealtimeAdmissionTimeout extends RimeTimeoutError {
3
+ }
4
+ export class RimeRealtimeError extends RimeError {
5
+ fault;
6
+ constructor(fault) {
7
+ super(`${fault.code}: ${fault.message}`, fault.requestId);
8
+ this.fault = fault;
9
+ }
10
+ }
@@ -0,0 +1,15 @@
1
+ import { type PCMFormat } from "../pcm.js";
2
+ /** One utterance of headerless signed PCM16 little-endian mono 16 kHz output.
3
+ * Fully consume each feed iterator before feeding the next chunk. Conversion
4
+ * advances one bounded source portion at a time so callers can apply backpressure.
5
+ */
6
+ export declare class InputAudio {
7
+ private readonly converter;
8
+ private readonly frameBytes;
9
+ private pending;
10
+ private finished;
11
+ constructor(format?: PCMFormat);
12
+ feed(data: Uint8Array): Generator<Buffer>;
13
+ /** Check EOF before committing; never silently pad or truncate a final frame. */
14
+ finish(): void;
15
+ }
@@ -0,0 +1,46 @@
1
+ import { RimeAudioFormatError, RimeInputError } from "../errors.js";
2
+ import { InputConverter, resolvePCMFormat } from "../pcm.js";
3
+ // Bound conversion work and temporary allocations for large source chunks.
4
+ const sourceBytes = 16384;
5
+ // Maximum decoded STT audio payload, excluding the protobuf envelope.
6
+ const messageBytes = 65536;
7
+ /** One utterance of headerless signed PCM16 little-endian mono 16 kHz output.
8
+ * Fully consume each feed iterator before feeding the next chunk. Conversion
9
+ * advances one bounded source portion at a time so callers can apply backpressure.
10
+ */
11
+ export class InputAudio {
12
+ converter;
13
+ frameBytes;
14
+ pending = Buffer.alloc(0);
15
+ finished = false;
16
+ constructor(format) {
17
+ const resolved = resolvePCMFormat(format);
18
+ this.converter = new InputConverter(resolved);
19
+ this.frameBytes = 2 * resolved.channels;
20
+ }
21
+ *feed(data) {
22
+ if (this.finished)
23
+ throw new RimeInputError("The audio input is finished");
24
+ if (!(data instanceof Uint8Array))
25
+ throw new RimeAudioFormatError("The audio source must yield PCM16 Uint8Array chunks");
26
+ for (let offset = 0; offset < data.length; offset += sourceBytes) {
27
+ const part = Buffer.concat([
28
+ this.pending,
29
+ data.subarray(offset, offset + sourceBytes),
30
+ ]);
31
+ const complete = part.length - (part.length % this.frameBytes);
32
+ this.pending = Buffer.from(part.subarray(complete));
33
+ if (complete) {
34
+ const converted = this.converter.process(part.subarray(0, complete));
35
+ for (let start = 0; start < converted.length; start += messageBytes)
36
+ yield converted.subarray(start, start + messageBytes);
37
+ }
38
+ }
39
+ }
40
+ /** Check EOF before committing; never silently pad or truncate a final frame. */
41
+ finish() {
42
+ this.finished = true;
43
+ if (this.pending.length)
44
+ throw new RimeAudioFormatError("The audio source ended with an incomplete PCM16 frame");
45
+ }
46
+ }
@@ -0,0 +1,17 @@
1
+ import { Credentials } from "../auth.js";
2
+ import { TranscriptStream } from "./stream.js";
3
+ import type { AudioSource, TranscriptionOptions, TranscribeOptions, Transcription } from "./types.js";
4
+ export declare class STT {
5
+ private readonly credentials;
6
+ private readonly checkOpen;
7
+ private connection;
8
+ private streams;
9
+ private readonly cancellation;
10
+ private readonly requests;
11
+ constructor(credentials: Credentials, checkOpen: () => void);
12
+ /** Transcribe complete WAV bytes (at most 16 MiB), without replay or retries. */
13
+ transcribe(audio: Uint8Array, options: TranscribeOptions): Promise<Transcription>;
14
+ private prepare;
15
+ stream(source: AudioSource, options: TranscriptionOptions): TranscriptStream;
16
+ close(): Promise<void>;
17
+ }
@@ -0,0 +1,121 @@
1
+ import { connect, metadata } from "../grpc.js";
2
+ import { timeout } from "../validation.js";
3
+ import { RimeInputError, RimeAudioFormatError, RimeResourceLimitError, RimeCancelledError, RimeTimeoutError, } from "../errors.js";
4
+ import { scope } from "../cancellation.js";
5
+ import { resolvePCMFormat } from "../pcm.js";
6
+ import { transport, requireSchema, transcribe, MAX_WAV_BYTES, } from "./transport.js";
7
+ import { TranscriptStream } from "./stream.js";
8
+ import { policy } from "./policy.js";
9
+ export class STT {
10
+ credentials;
11
+ checkOpen;
12
+ connection = null;
13
+ streams = new Set();
14
+ cancellation = new AbortController();
15
+ requests = new Set();
16
+ constructor(credentials, checkOpen) {
17
+ this.credentials = credentials;
18
+ this.checkOpen = checkOpen;
19
+ }
20
+ /** Transcribe complete WAV bytes (at most 16 MiB), without replay or retries. */
21
+ transcribe(audio, options) {
22
+ this.checkOpen();
23
+ if (!(audio instanceof Uint8Array) || audio.byteLength === 0)
24
+ throw new RimeAudioFormatError("audio must contain complete WAV bytes");
25
+ if (audio.byteLength > MAX_WAV_BYTES)
26
+ throw new RimeResourceLimitError("WAV input exceeds 16 MiB");
27
+ validateOptions(options);
28
+ const settings = {
29
+ ...options,
30
+ contextTerms: [...(options.contextTerms ?? [])],
31
+ };
32
+ const data = Buffer.from(audio);
33
+ const deadline = scope([options.signal, this.cancellation.signal], timeout(options.timeout) ?? undefined, new RimeTimeoutError("Overall transcription deadline expired"));
34
+ const operation = (async () => {
35
+ try {
36
+ const prepared = await this.prepare(deadline.signal, policy.target);
37
+ return await transcribe(prepared.client, prepared.metadata, data, settings, deadline.signal);
38
+ }
39
+ catch (error) {
40
+ if (deadline.signal.aborted &&
41
+ !(error instanceof RimeTimeoutError) &&
42
+ !(error instanceof RimeCancelledError)) {
43
+ if (deadline.signal.reason instanceof RimeTimeoutError)
44
+ throw deadline.signal.reason;
45
+ throw new RimeCancelledError("Transcription cancelled");
46
+ }
47
+ throw error;
48
+ }
49
+ finally {
50
+ deadline.dispose();
51
+ }
52
+ })();
53
+ this.requests.add(operation);
54
+ void operation.then(() => this.requests.delete(operation), () => this.requests.delete(operation));
55
+ return operation;
56
+ }
57
+ async prepare(signal, target) {
58
+ this.checkOpen();
59
+ requireSchema();
60
+ signal.throwIfAborted();
61
+ const headers = metadata(this.credentials.authorization());
62
+ this.connection ??= transport.makeClient(target);
63
+ await connect(this.connection, signal, policy.connectionTimeout);
64
+ signal.throwIfAborted();
65
+ return { client: this.connection, metadata: headers };
66
+ }
67
+ stream(source, options) {
68
+ this.checkOpen();
69
+ if (!source || typeof source[Symbol.asyncIterator] !== "function")
70
+ throw new RimeInputError("audio must be an async iterable of Uint8Array chunks");
71
+ validateOptions(options);
72
+ const outputContract = options.outputContract ?? "revised_hypotheses";
73
+ if (outputContract !== "revised_hypotheses" &&
74
+ outputContract !== "append_fragments")
75
+ throw new RimeInputError("outputContract must be revised_hypotheses or append_fragments");
76
+ const mode = options.mode ?? "written";
77
+ const terms = options.contextTerms ?? [];
78
+ const format = resolvePCMFormat(options.inputFormat);
79
+ const target = policy.target;
80
+ const stream = new TranscriptStream({
81
+ checkOpen: this.checkOpen,
82
+ forget: (stream) => this.streams.delete(stream),
83
+ prepare: (signal) => this.prepare(signal, target),
84
+ }, source, {
85
+ language: options.language,
86
+ outputContract,
87
+ mode,
88
+ contextTerms: [...terms],
89
+ inputFormat: format,
90
+ timeout: timeout(options.timeout),
91
+ signal: options.signal,
92
+ });
93
+ this.streams.add(stream);
94
+ return stream;
95
+ }
96
+ async close() {
97
+ this.cancellation.abort(new RimeCancelledError("Client closed"));
98
+ try {
99
+ const results = await Promise.allSettled([
100
+ ...[...this.streams].map((stream) => stream.cancel()),
101
+ ...[...this.requests].map((request) => request.catch(() => { })),
102
+ ]);
103
+ for (const result of results)
104
+ if (result.status === "rejected")
105
+ throw result.reason;
106
+ }
107
+ finally {
108
+ this.connection?.close();
109
+ }
110
+ }
111
+ }
112
+ function validateOptions(options) {
113
+ if (!options || typeof options.language !== "string")
114
+ throw new RimeInputError("language must be a string");
115
+ const mode = options.mode ?? "written";
116
+ if (mode !== "written" && mode !== "verbatim")
117
+ throw new RimeInputError("mode must be written or verbatim");
118
+ const terms = options.contextTerms ?? [];
119
+ if (!Array.isArray(terms) || terms.some((term) => typeof term !== "string"))
120
+ throw new RimeInputError("contextTerms must be an array of strings");
121
+ }
@@ -0,0 +1,11 @@
1
+ /** Independent resource and transport limits for recognition. Durations are seconds. */
2
+ export declare const policy: {
3
+ target: string;
4
+ connectionTimeout: number;
5
+ acceptanceTimeout: number;
6
+ completionTimeout: number;
7
+ cleanupTimeout: number;
8
+ receiveBytes: number;
9
+ transcriptBytes: number;
10
+ queuedUpdates: number;
11
+ };
@@ -0,0 +1,11 @@
1
+ /** Independent resource and transport limits for recognition. Durations are seconds. */
2
+ export const policy = {
3
+ target: "stt.api.rime.ai:443",
4
+ connectionTimeout: 10,
5
+ acceptanceTimeout: 10,
6
+ completionTimeout: 120,
7
+ cleanupTimeout: 2,
8
+ receiveBytes: 1048576 + 65536,
9
+ transcriptBytes: 1048576,
10
+ queuedUpdates: 16,
11
+ };
@@ -0,0 +1,14 @@
1
+ import * as schema from "@rimelabs/api";
2
+ import type { TranscriptionPartial, TranscriptionDelta, TranscriptionFinal, TranscriptionOutputContract } from "./types.js";
3
+ /** Validate the wire sequence before exposing any final result. */
4
+ export declare class TranscriptState {
5
+ private readonly textLimit;
6
+ private readonly outputContract;
7
+ language: string | null;
8
+ private revision;
9
+ private text;
10
+ private final;
11
+ constructor(textLimit: number, outputContract?: TranscriptionOutputContract);
12
+ accept(message: schema.StreamingTranscriptionResponse, inputDone: boolean): TranscriptionPartial | TranscriptionDelta | null;
13
+ finish(): TranscriptionFinal;
14
+ }
@@ -0,0 +1,76 @@
1
+ import * as schema from "@rimelabs/api";
2
+ import { RimeStreamError, RimeResourceLimitError } from "../errors.js";
3
+ /** Validate the wire sequence before exposing any final result. */
4
+ export class TranscriptState {
5
+ textLimit;
6
+ outputContract;
7
+ language = null;
8
+ revision = 0n;
9
+ text = "";
10
+ final = null;
11
+ constructor(textLimit, outputContract = "revised_hypotheses") {
12
+ this.textLimit = textLimit;
13
+ this.outputContract = outputContract;
14
+ }
15
+ accept(message, inputDone) {
16
+ const payload = message.payload;
17
+ if (payload.case === undefined)
18
+ return null;
19
+ if (this.final)
20
+ throw new RimeStreamError("Received a message after transcription completion");
21
+ if (payload.case === "accepted") {
22
+ const accepted = payload.value;
23
+ if (this.language !== null)
24
+ throw new RimeStreamError("Received duplicate transcription acceptance");
25
+ if (accepted.outputContract !==
26
+ (this.outputContract === "append_fragments"
27
+ ? schema.StreamingOutputContract.APPEND_FRAGMENTS
28
+ : schema.StreamingOutputContract.REVISED_HYPOTHESES))
29
+ throw new RimeStreamError("The service accepted a different transcript contract");
30
+ if (!accepted.language?.tag || !accepted.language.source)
31
+ throw new RimeStreamError("The service did not confirm a resolved language");
32
+ this.language = accepted.language.tag;
33
+ return null;
34
+ }
35
+ if (this.language === null)
36
+ throw new RimeStreamError("Received a transcript before acceptance");
37
+ const append = this.outputContract === "append_fragments";
38
+ if (payload.case === "delta" && append) {
39
+ const text = payload.value.text;
40
+ if (Buffer.byteLength(this.text) + Buffer.byteLength(text) >
41
+ this.textLimit)
42
+ throw new RimeResourceLimitError("The transcript exceeds the SDK text limit");
43
+ this.text += text;
44
+ return { kind: "delta", text };
45
+ }
46
+ if (payload.case !== "hypothesis" && payload.case !== "done")
47
+ throw new RimeStreamError("Received an unexpected transcription response");
48
+ if (payload.case === "hypothesis" && append)
49
+ throw new RimeStreamError("Received an unexpected transcription response");
50
+ const value = payload.value;
51
+ if (Buffer.byteLength(value.text, "utf8") > this.textLimit)
52
+ throw new RimeResourceLimitError("The transcript exceeds the SDK text limit");
53
+ if (payload.case === "hypothesis") {
54
+ if (value.revision <= this.revision ||
55
+ (this.revision === 0n && value.revision !== 1n))
56
+ throw new RimeStreamError("Transcript revisions are not increasing from one");
57
+ this.revision = value.revision;
58
+ this.text = value.text;
59
+ return { kind: "partial", text: value.text };
60
+ }
61
+ if (!inputDone)
62
+ throw new RimeStreamError("The service completed before input finished");
63
+ if (value.revision !== this.revision || value.text !== this.text)
64
+ throw new RimeStreamError("Final transcript does not match prior updates");
65
+ const language = payload.value.language;
66
+ if (language?.tag !== this.language || !language.source)
67
+ throw new RimeStreamError("Final language does not match the accepted language");
68
+ this.final = { kind: "final", text: value.text, language: language.tag };
69
+ return null;
70
+ }
71
+ finish() {
72
+ if (!this.final)
73
+ throw new RimeStreamError("The service ended without a final transcript");
74
+ return this.final;
75
+ }
76
+ }