@rimelabs/sdk 0.1.0-alpha.16 → 0.1.0-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.0-alpha.17](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.16...typescript-v0.1.0-alpha.17) (2026-10-16)
4
+
5
+
6
+ ### Features
7
+
8
+ * add unary STT transcription to all SDKs ([#80](https://github.com/rimelabs/rime-sdk/issues/80)) ([39427eb](https://github.com/rimelabs/rime-sdk/commit/39427eba7738e4f2fe0514a2cddaa219137dcc4d))
9
+
3
10
  ## [0.1.0-alpha.16](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.15...typescript-v0.1.0-alpha.16) (2026-10-16)
4
11
 
5
12
 
package/README.md CHANGED
@@ -224,12 +224,51 @@ and model/language support.
224
224
 
225
225
  ## STT
226
226
 
227
+ ### Transcribe a complete WAV recording
228
+
229
+ For a complete recording, use the native unary `Transcribe` RPC through the SDK.
230
+ Pass **complete WAV bytes, including the header**, up to **16 MiB (16,777,216
231
+ bytes)**. PCM16 mono 16 kHz WAV is the portable format. The SDK sends the file
232
+ unchanged; it does not decode, resample, or add a header. The service validates
233
+ its encoding and the supplied language and recognition hints.
234
+
235
+ The result is a `Transcription` containing final text, resolved language, and a
236
+ request ID when available. Silence may return empty text. There are no partial
237
+ updates or automatic retries. TTS client settings do not apply. An optional
238
+ overall timeout includes connection and upload; the SDK also bounds connection
239
+ time and the unary request (120 seconds). Timeout, cancellation, and service
240
+ errors use the existing SDK error categories. A request ID is included when the
241
+ transport has made it available.
242
+
243
+ ```typescript
244
+ import { readFile } from "node:fs/promises";
245
+ import { Rime } from "@rimelabs/sdk";
246
+
247
+ const client = new Rime();
248
+ try {
249
+ const result = await client.stt.transcribe(await readFile("utterance.wav"), {
250
+ language: "en",
251
+ mode: "written",
252
+ contextTerms: ["Super-G", "USOC"],
253
+ timeout: 120,
254
+ });
255
+ console.log(result.text, result.language, result.requestId);
256
+ } finally {
257
+ await client.close();
258
+ }
259
+ ```
260
+
261
+ `TranscribeOptions` also accepts an `AbortSignal` as `signal`. Aborting it or
262
+ closing the client cancels pending requests with `RimeCancelledError`.
263
+
264
+ ### Stream an utterance
265
+
227
266
  Use `client.stt.stream(audioChunks, options)` to transcribe one utterance.
228
267
  The required source is an async iterable of headerless signed PCM16 little-endian
229
268
  `Uint8Array` chunks (`Buffer` also works). Ending the source ends the utterance;
230
269
  your application decides when to stop.
231
270
 
232
- STT uses streaming gRPC over TLS to `stt.api.rime.ai:443`, with the client's API
271
+ STT uses gRPC over TLS to `stt.api.rime.ai:443`, with the client's API
233
272
  key. There is no STT model, endpoint, or protocol selector. TTS client settings,
234
273
  including its timeout, do not apply to STT.
235
274
 
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AudioSource, TranscriptionOptions } from "./stt/types.js";
1
+ import type { AudioSource, TranscriptionOptions, TranscribeOptions, Transcription } from "./stt/types.js";
2
2
  import type { TranscriptStream } from "./stt/stream.js";
3
3
  import { type SynthesisOptions, type DiscoveryOptions, type VoiceListOptions } from "./tts/client.js";
4
4
  import { type AudioStream, type TextSource } from "./tts/stream.js";
@@ -17,6 +17,8 @@ export declare class Rime {
17
17
  private readonly ttsClient;
18
18
  private readonly sttClient;
19
19
  readonly stt: {
20
+ /** Transcribe complete WAV bytes. PCM16 mono 16 kHz is the portable format; maximum 16 MiB. */
21
+ transcribe: (audio: Uint8Array, options: TranscribeOptions) => Promise<Transcription>;
20
22
  stream: (audio: AudioSource, options: TranscriptionOptions) => TranscriptStream;
21
23
  };
22
24
  private readonly realtimeClient;
package/dist/client.js CHANGED
@@ -27,6 +27,7 @@ export class Rime {
27
27
  this.credentials = new Credentials(key, deployment);
28
28
  this.sttClient = new STT(this.credentials, () => this.checkOpen());
29
29
  this.stt = {
30
+ transcribe: (audio, options) => this.sttClient.transcribe(audio, options),
30
31
  stream: (audio, options) => this.sttClient.stream(audio, options),
31
32
  };
32
33
  this.realtimeClient = new Realtime(this.credentials, () => this.checkOpen());
package/dist/index.d.ts CHANGED
@@ -7,4 +7,4 @@ export * from "./errors.js";
7
7
  export { RealtimeSession } from "./realtime/session.js";
8
8
  export * from "./realtime/types.js";
9
9
  export { TranscriptStream } from "./stt/stream.js";
10
- export type { AudioSource, TranscriptionOptions, TranscriptionMode, TranscriptionPartial, TranscriptionFinal, TranscriptionUpdate, } from "./stt/types.js";
10
+ export type { AudioSource, TranscribeOptions, Transcription, TranscriptionOptions, TranscriptionMode, TranscriptionPartial, TranscriptionFinal, TranscriptionUpdate, } from "./stt/types.js";
@@ -1,12 +1,17 @@
1
1
  import { Credentials } from "../auth.js";
2
2
  import { TranscriptStream } from "./stream.js";
3
- import type { AudioSource, TranscriptionOptions } from "./types.js";
3
+ import type { AudioSource, TranscriptionOptions, TranscribeOptions, Transcription } from "./types.js";
4
4
  export declare class STT {
5
5
  private readonly credentials;
6
6
  private readonly checkOpen;
7
7
  private connection;
8
8
  private streams;
9
+ private readonly cancellation;
10
+ private readonly requests;
9
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;
10
15
  stream(source: AudioSource, options: TranscriptionOptions): TranscriptStream;
11
16
  close(): Promise<void>;
12
17
  }
@@ -1,8 +1,9 @@
1
1
  import { connect, metadata } from "../grpc.js";
2
2
  import { timeout } from "../validation.js";
3
- import { RimeInputError } from "../errors.js";
3
+ import { RimeInputError, RimeAudioFormatError, RimeResourceLimitError, RimeCancelledError, RimeTimeoutError, } from "../errors.js";
4
+ import { scope } from "../cancellation.js";
4
5
  import { resolvePCMFormat } from "../pcm.js";
5
- import { transport, requireSchema } from "./transport.js";
6
+ import { transport, requireSchema, transcribe, MAX_WAV_BYTES, } from "./transport.js";
6
7
  import { TranscriptStream } from "./stream.js";
7
8
  import { policy } from "./policy.js";
8
9
  export class STT {
@@ -10,40 +11,72 @@ export class STT {
10
11
  checkOpen;
11
12
  connection = null;
12
13
  streams = new Set();
14
+ cancellation = new AbortController();
15
+ requests = new Set();
13
16
  constructor(credentials, checkOpen) {
14
17
  this.credentials = credentials;
15
18
  this.checkOpen = checkOpen;
16
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
+ }
17
67
  stream(source, options) {
18
68
  this.checkOpen();
19
69
  if (!source || typeof source[Symbol.asyncIterator] !== "function")
20
70
  throw new RimeInputError("audio must be an async iterable of Uint8Array chunks");
21
- if (!options || typeof options.language !== "string")
22
- throw new RimeInputError("language must be a string");
71
+ validateOptions(options);
23
72
  const mode = options.mode ?? "written";
24
- if (mode !== "written" && mode !== "verbatim")
25
- throw new RimeInputError("mode must be written or verbatim");
26
73
  const terms = options.contextTerms ?? [];
27
- if (!Array.isArray(terms) || terms.some((term) => typeof term !== "string"))
28
- throw new RimeInputError("contextTerms must be an array of strings");
29
74
  const format = resolvePCMFormat(options.inputFormat);
30
75
  const target = policy.target;
31
76
  const stream = new TranscriptStream({
32
77
  checkOpen: this.checkOpen,
33
78
  forget: (stream) => this.streams.delete(stream),
34
- prepare: async (signal) => {
35
- this.checkOpen();
36
- requireSchema();
37
- signal.throwIfAborted();
38
- const headers = metadata(this.credentials.authorization());
39
- this.connection ??= transport.makeClient(target);
40
- await connect(this.connection, signal, policy.connectionTimeout);
41
- signal.throwIfAborted();
42
- return {
43
- client: this.connection,
44
- metadata: headers,
45
- };
46
- },
79
+ prepare: (signal) => this.prepare(signal, target),
47
80
  }, source, {
48
81
  language: options.language,
49
82
  mode,
@@ -56,8 +89,12 @@ export class STT {
56
89
  return stream;
57
90
  }
58
91
  async close() {
92
+ this.cancellation.abort(new RimeCancelledError("Client closed"));
59
93
  try {
60
- const results = await Promise.allSettled([...this.streams].map((stream) => stream.cancel()));
94
+ const results = await Promise.allSettled([
95
+ ...[...this.streams].map((stream) => stream.cancel()),
96
+ ...[...this.requests].map((request) => request.catch(() => { })),
97
+ ]);
61
98
  for (const result of results)
62
99
  if (result.status === "rejected")
63
100
  throw result.reason;
@@ -67,3 +104,13 @@ export class STT {
67
104
  }
68
105
  }
69
106
  }
107
+ function validateOptions(options) {
108
+ if (!options || typeof options.language !== "string")
109
+ throw new RimeInputError("language must be a string");
110
+ const mode = options.mode ?? "written";
111
+ if (mode !== "written" && mode !== "verbatim")
112
+ throw new RimeInputError("mode must be written or verbatim");
113
+ const terms = options.contextTerms ?? [];
114
+ if (!Array.isArray(terms) || terms.some((term) => typeof term !== "string"))
115
+ throw new RimeInputError("contextTerms must be an array of strings");
116
+ }
@@ -1,11 +1,13 @@
1
1
  import * as grpc from "@grpc/grpc-js";
2
2
  import * as schema from "@rimelabs/api";
3
3
  import { BidiCall } from "../grpc.js";
4
- import type { TranscriptionMode } from "./types.js";
4
+ import type { TranscriptionMode, TranscribeOptions, Transcription } from "./types.js";
5
+ export declare const MAX_WAV_BYTES: number;
5
6
  export declare function requireSchema(): void;
6
7
  export declare const transport: {
7
8
  makeClient: (target: string) => grpc.Client;
8
9
  };
10
+ export declare function transcribe(client: grpc.Client, auth: grpc.Metadata, audio: Uint8Array, options: TranscribeOptions, signal: AbortSignal): Promise<Transcription>;
9
11
  export declare class TranscriptionCall extends BidiCall<schema.StreamingTranscriptionRequest, schema.StreamingTranscriptionResponse> {
10
12
  constructor(client: grpc.Client, auth: grpc.Metadata, signal: AbortSignal);
11
13
  start(language: string, mode: TranscriptionMode, contextTerms: readonly string[]): Promise<void>;
@@ -1,9 +1,11 @@
1
1
  import * as grpc from "@grpc/grpc-js";
2
2
  import { create } from "@bufbuild/protobuf";
3
3
  import * as schema from "@rimelabs/api";
4
- import { BidiCall, serializer, deserializer } from "../grpc.js";
5
- import { RimeInputError } from "../errors.js";
4
+ import { BidiCall, serializer, deserializer, requestId, nativeError, } from "../grpc.js";
5
+ import { abortable, scope } from "../cancellation.js";
6
+ import { RimeInputError, RimeStreamError, RimeResourceLimitError, RimeTimeoutError, RimeCancelledError, } from "../errors.js";
6
7
  import { policy } from "./policy.js";
8
+ export const MAX_WAV_BYTES = 16 * 1024 * 1024;
7
9
  export function requireSchema() {
8
10
  if (!schema.SpeechToText)
9
11
  throw new RimeInputError("STT requires @rimelabs/api with SpeechToText definitions");
@@ -12,9 +14,54 @@ export const transport = {
12
14
  makeClient: (target) => new grpc.Client(target, grpc.credentials.createSsl(), {
13
15
  "grpc.enable_retries": 0,
14
16
  "grpc.max_receive_message_length": policy.receiveBytes,
15
- "grpc.max_send_message_length": 131072,
17
+ "grpc.max_send_message_length": MAX_WAV_BYTES + 65536,
16
18
  }),
17
19
  };
20
+ export async function transcribe(client, auth, audio, options, signal) {
21
+ const method = schema.SpeechToText.method.transcribe;
22
+ const deadline = scope([signal], policy.completionTimeout, new RimeTimeoutError("Transcription request timed out"));
23
+ let call;
24
+ let id = null;
25
+ try {
26
+ deadline.signal.throwIfAborted();
27
+ const result = await abortable(new Promise((resolve, reject) => {
28
+ call = client.makeUnaryRequest(`/${schema.SpeechToText.typeName}/${method.name}`, serializer(method.input), deserializer(method.output), create(method.input, {
29
+ audio,
30
+ options: {
31
+ language: options.language,
32
+ mode: options.mode === "verbatim"
33
+ ? schema.TranscriptionMode.VERBATIM
34
+ : schema.TranscriptionMode.WRITTEN,
35
+ contextTerms: [...(options.contextTerms ?? [])],
36
+ },
37
+ }), auth, (error, response) => (error ? reject(error) : resolve(response)));
38
+ call.on("metadata", (headers) => {
39
+ id = requestId(headers);
40
+ });
41
+ call.on("status", (status) => {
42
+ id ??= requestId(status.metadata);
43
+ });
44
+ }), deadline.signal);
45
+ if (!result.language?.tag || !result.language.source)
46
+ throw new RimeStreamError("The service did not confirm a resolved language", id);
47
+ if (Buffer.byteLength(result.text) > policy.transcriptBytes)
48
+ throw new RimeResourceLimitError("The transcript exceeds the SDK text limit", id);
49
+ return { text: result.text, language: result.language.tag, requestId: id };
50
+ }
51
+ catch (error) {
52
+ if (deadline.signal.aborted) {
53
+ const reason = deadline.signal.reason;
54
+ if (reason instanceof RimeTimeoutError)
55
+ throw new RimeTimeoutError(reason.message, id);
56
+ throw new RimeCancelledError("Transcription cancelled", id);
57
+ }
58
+ throw nativeError(error, id);
59
+ }
60
+ finally {
61
+ call?.cancel();
62
+ deadline.dispose();
63
+ }
64
+ }
18
65
  export class TranscriptionCall extends BidiCall {
19
66
  constructor(client, auth, signal) {
20
67
  requireSchema();
@@ -32,3 +32,11 @@ export interface TranscriptionFinal {
32
32
  readonly language: string;
33
33
  }
34
34
  export type TranscriptionUpdate = TranscriptionPartial | TranscriptionFinal;
35
+ /** Options for a complete WAV recording. Audio is sent unchanged. */
36
+ export type TranscribeOptions = Omit<TranscriptionOptions, "inputFormat">;
37
+ /** A successful whole-recording transcription. Silence may produce empty text. */
38
+ export interface Transcription {
39
+ readonly text: string;
40
+ readonly language: string;
41
+ readonly requestId: string | null;
42
+ }
package/docs/stt.md CHANGED
@@ -1,4 +1,43 @@
1
- # Streaming speech recognition
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
2
41
 
3
42
  `client.stt.stream` transcribes one utterance from an async source of `Uint8Array` chunks (including Node buffers).
4
43
  The SDK handles the gRPC protocol, authentication, audio conversion, transcript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelabs/sdk",
3
- "version": "0.1.0-alpha.16",
3
+ "version": "0.1.0-alpha.17",
4
4
  "description": "Rime streaming TTS, STT and Prism realtime SDK for Node.js",
5
5
  "license": "MIT",
6
6
  "type": "module",