@rimelabs/sdk 0.1.0-alpha.1 → 0.1.0-alpha.12

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 (67) hide show
  1. package/CHANGELOG.md +96 -11
  2. package/LICENSE-PRISM +201 -0
  3. package/NOTICE-PRISM +9 -0
  4. package/README.md +175 -63
  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 +23 -23
  10. package/dist/client.js +39 -92
  11. package/dist/grpc.d.ts +27 -0
  12. package/dist/{transport.js → grpc.js} +36 -106
  13. package/dist/index.d.ts +6 -2
  14. package/dist/index.js +5 -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 +156 -0
  23. package/dist/realtime/protocol.d.ts +80 -0
  24. package/dist/realtime/protocol.js +354 -0
  25. package/dist/realtime/session.d.ts +67 -0
  26. package/dist/realtime/session.js +731 -0
  27. package/dist/realtime/transport.d.ts +16 -0
  28. package/dist/realtime/transport.js +83 -0
  29. package/dist/realtime/types.d.ts +186 -0
  30. package/dist/realtime/types.js +10 -0
  31. package/dist/stt/audio.d.ts +15 -0
  32. package/dist/stt/audio.js +46 -0
  33. package/dist/stt/client.d.ts +13 -0
  34. package/dist/stt/client.js +73 -0
  35. package/dist/stt/policy.d.ts +11 -0
  36. package/dist/stt/policy.js +11 -0
  37. package/dist/stt/protocol.d.ts +13 -0
  38. package/dist/stt/protocol.js +61 -0
  39. package/dist/stt/queue.d.ts +16 -0
  40. package/dist/stt/queue.js +45 -0
  41. package/dist/stt/stream.d.ts +48 -0
  42. package/dist/stt/stream.js +224 -0
  43. package/dist/stt/transport.d.ts +13 -0
  44. package/dist/stt/transport.js +45 -0
  45. package/dist/stt/types.d.ts +34 -0
  46. package/dist/stt/types.js +1 -0
  47. package/dist/{audio.js → tts/audio.js} +3 -2
  48. package/dist/tts/client.d.ts +39 -0
  49. package/dist/tts/client.js +121 -0
  50. package/dist/{policy.d.ts → tts/policy.d.ts} +2 -2
  51. package/dist/tts/policy.js +41 -0
  52. package/dist/{sentences.js → tts/sentences.js} +2 -2
  53. package/dist/{stream.d.ts → tts/stream.d.ts} +1 -1
  54. package/dist/{stream.js → tts/stream.js} +5 -8
  55. package/dist/{transport.d.ts → tts/transport.d.ts} +3 -11
  56. package/dist/tts/transport.js +116 -0
  57. package/dist/validation.d.ts +5 -0
  58. package/dist/validation.js +28 -0
  59. package/docs/realtime.md +220 -0
  60. package/docs/stt.md +103 -0
  61. package/docs/tts.md +139 -0
  62. package/package.json +14 -6
  63. package/dist/policy.js +0 -73
  64. /package/dist/{audio.d.ts → tts/audio.d.ts} +0 -0
  65. /package/dist/{queue.d.ts → tts/queue.d.ts} +0 -0
  66. /package/dist/{queue.js → tts/queue.js} +0 -0
  67. /package/dist/{sentences.d.ts → tts/sentences.d.ts} +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,186 @@
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
+ export interface ToolDefinition {
18
+ readonly name: string;
19
+ readonly parameters: JsonObject;
20
+ readonly description?: string;
21
+ }
22
+ export interface RealtimeConnectOptions extends RealtimeOperationOptions {
23
+ readonly endpoint: string;
24
+ readonly model?: "prism";
25
+ readonly voice?: string | null;
26
+ readonly instructions?: string | null;
27
+ readonly tools?: readonly ToolDefinition[];
28
+ readonly interruptOnSpeech?: boolean;
29
+ readonly timeouts?: RealtimeTimeouts;
30
+ }
31
+ export interface ReplyOptions extends RealtimeOperationOptions {
32
+ readonly instruction?: string | null;
33
+ readonly toolCall?: ToolCallRef | null;
34
+ }
35
+ export interface AudioChunk {
36
+ readonly data: Uint8Array;
37
+ readonly format?: PCMFormat;
38
+ }
39
+ export interface ResponseRef {
40
+ readonly sessionId: string;
41
+ readonly responseId: string;
42
+ }
43
+ export interface ItemRef {
44
+ readonly sessionId: string;
45
+ readonly itemId: string;
46
+ }
47
+ export interface ToolCallRef extends ResponseRef {
48
+ readonly callId: string;
49
+ }
50
+ export interface OutputRef {
51
+ readonly response: ResponseRef;
52
+ readonly itemId: string;
53
+ readonly outputIndex: number;
54
+ readonly contentIndex: number;
55
+ }
56
+ export interface SessionInfo {
57
+ readonly sessionId: string;
58
+ readonly model: "prism";
59
+ readonly voice: string;
60
+ readonly interruptOnSpeech: boolean;
61
+ readonly toolResultTimeoutS: number | null;
62
+ readonly toolContinuationTimeoutS: number | null;
63
+ }
64
+ export interface RealtimeFault {
65
+ readonly code: string;
66
+ readonly message: string;
67
+ readonly scope: "event" | "utterance" | "response" | "tool_roundtrip" | "session" | "unknown";
68
+ readonly requestId: string | null;
69
+ readonly responseId: string | null;
70
+ readonly itemId: string | null;
71
+ readonly callIds: readonly string[];
72
+ readonly correlationId: string | null;
73
+ readonly parameter: string | null;
74
+ }
75
+ export declare class RealtimeAdmissionTimeout extends RimeTimeoutError {
76
+ }
77
+ export declare class RimeRealtimeError extends RimeError {
78
+ readonly fault: RealtimeFault;
79
+ constructor(fault: RealtimeFault);
80
+ }
81
+ export interface InputReady {
82
+ readonly kind: "input.ready";
83
+ }
84
+ export interface SpeechStarted {
85
+ readonly kind: "speech.started";
86
+ readonly itemId: string;
87
+ readonly audioStartMs: number;
88
+ }
89
+ export interface SpeechStopped {
90
+ readonly kind: "speech.stopped";
91
+ readonly itemId: string;
92
+ readonly audioEndMs: number;
93
+ }
94
+ export interface InputCommitted {
95
+ readonly kind: "input.committed";
96
+ readonly itemId: string;
97
+ }
98
+ export interface TranscriptDelta {
99
+ readonly kind: "transcript.delta";
100
+ readonly itemId: string;
101
+ readonly delta: string;
102
+ }
103
+ export interface TranscriptFinal {
104
+ readonly kind: "transcript.final";
105
+ readonly itemId: string;
106
+ readonly text: string;
107
+ }
108
+ export interface TranscriptFailed {
109
+ readonly kind: "transcript.failed";
110
+ readonly itemId: string;
111
+ readonly error: RealtimeFault;
112
+ }
113
+ export interface ResponseStarted {
114
+ readonly kind: "response.started";
115
+ readonly response: ResponseRef;
116
+ readonly cause: "speech" | "text" | "proactive" | "tools" | "unknown";
117
+ readonly parent: ResponseRef | null;
118
+ readonly inputItemId: string | null;
119
+ }
120
+ export interface MessageStarted {
121
+ readonly kind: "message.started";
122
+ readonly output: OutputRef;
123
+ }
124
+ export interface TextDelta {
125
+ readonly kind: "text.delta";
126
+ readonly output: OutputRef;
127
+ readonly delta: string;
128
+ }
129
+ export interface TextDone {
130
+ readonly kind: "text.done";
131
+ readonly output: OutputRef;
132
+ readonly text: string;
133
+ }
134
+ export interface AudioDelta {
135
+ readonly kind: "audio.delta";
136
+ readonly output: OutputRef;
137
+ readonly audio: {
138
+ readonly data: Uint8Array;
139
+ readonly format: Required<PCMFormat>;
140
+ };
141
+ }
142
+ export interface AudioDone {
143
+ readonly kind: "audio.done";
144
+ readonly output: OutputRef;
145
+ }
146
+ export interface ToolCall {
147
+ readonly kind: "tool.call";
148
+ readonly call: ToolCallRef;
149
+ readonly itemId: string;
150
+ readonly name: string;
151
+ readonly arguments: JsonObject;
152
+ }
153
+ export interface ResponseAbandoned {
154
+ readonly kind: "response.abandoned";
155
+ readonly response: ResponseRef;
156
+ }
157
+ export interface ResponseEnded {
158
+ readonly kind: "response.ended";
159
+ readonly response: ResponseRef;
160
+ readonly status: "completed" | "cancelled" | "failed" | "unknown";
161
+ readonly reason: string | null;
162
+ }
163
+ export interface FaultEvent {
164
+ readonly kind: "error";
165
+ readonly error: RealtimeFault;
166
+ }
167
+ export type InputEvent = InputReady | SpeechStarted | SpeechStopped | InputCommitted | TranscriptDelta | TranscriptFinal | TranscriptFailed;
168
+ export type ResponseEvent = ResponseStarted | ResponseAbandoned | MessageStarted | TextDelta | TextDone | AudioDelta | AudioDone | ToolCall | ResponseEnded;
169
+ export interface SessionEvent {
170
+ readonly sessionId: string;
171
+ readonly eventId: string;
172
+ readonly requestId: string | null;
173
+ readonly payload: InputEvent | ResponseEvent | FaultEvent;
174
+ }
175
+ export interface PlaybackInterrupted {
176
+ readonly kind: "interrupted";
177
+ readonly output: OutputRef;
178
+ readonly audioEndMs: number;
179
+ }
180
+ export interface PlaybackFinished {
181
+ readonly kind: "finished";
182
+ readonly response: ResponseRef;
183
+ readonly playedMs?: number | null;
184
+ readonly audibleTailMs?: number | null;
185
+ }
186
+ 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,13 @@
1
+ import { Credentials } from "../auth.js";
2
+ import { TranscriptStream } from "./stream.js";
3
+ import type { AudioSource, TranscriptionOptions } from "./types.js";
4
+ export declare class STT {
5
+ private readonly credentials;
6
+ private readonly endpoint;
7
+ private readonly checkOpen;
8
+ private connection;
9
+ private streams;
10
+ constructor(credentials: Credentials, endpoint: string | null | undefined, checkOpen: () => void);
11
+ stream(source: AudioSource, options: TranscriptionOptions): TranscriptStream;
12
+ close(): Promise<void>;
13
+ }
@@ -0,0 +1,73 @@
1
+ import { connect, metadata } from "../grpc.js";
2
+ import { endpointAddress, timeout } from "../validation.js";
3
+ import { RimeInputError } from "../errors.js";
4
+ import { resolvePCMFormat } from "../pcm.js";
5
+ import { transport, requireSchema } from "./transport.js";
6
+ import { TranscriptStream } from "./stream.js";
7
+ import { policy } from "./policy.js";
8
+ export class STT {
9
+ credentials;
10
+ endpoint;
11
+ checkOpen;
12
+ connection = null;
13
+ streams = new Set();
14
+ constructor(credentials, endpoint, checkOpen) {
15
+ this.credentials = credentials;
16
+ this.endpoint = endpoint;
17
+ this.checkOpen = checkOpen;
18
+ }
19
+ stream(source, options) {
20
+ this.checkOpen();
21
+ if (!source || typeof source[Symbol.asyncIterator] !== "function")
22
+ throw new RimeInputError("audio must be an async iterable of Uint8Array chunks");
23
+ if (!options || typeof options.language !== "string")
24
+ throw new RimeInputError("language must be a string");
25
+ const mode = options.mode ?? "written";
26
+ if (mode !== "written" && mode !== "verbatim")
27
+ throw new RimeInputError("mode must be written or verbatim");
28
+ const terms = options.contextTerms ?? [];
29
+ if (!Array.isArray(terms) || terms.some((term) => typeof term !== "string"))
30
+ throw new RimeInputError("contextTerms must be an array of strings");
31
+ const format = resolvePCMFormat(options.inputFormat);
32
+ const target = this.endpoint == null
33
+ ? policy.target
34
+ : endpointAddress(this.endpoint).target;
35
+ const stream = new TranscriptStream({
36
+ checkOpen: this.checkOpen,
37
+ forget: (stream) => this.streams.delete(stream),
38
+ prepare: async (signal) => {
39
+ this.checkOpen();
40
+ requireSchema();
41
+ signal.throwIfAborted();
42
+ const headers = metadata(this.credentials.authorization());
43
+ this.connection ??= transport.makeClient(target);
44
+ await connect(this.connection, signal, policy.connectionTimeout);
45
+ signal.throwIfAborted();
46
+ return {
47
+ client: this.connection,
48
+ metadata: headers,
49
+ };
50
+ },
51
+ }, source, {
52
+ language: options.language,
53
+ mode,
54
+ contextTerms: [...terms],
55
+ inputFormat: format,
56
+ timeout: timeout(options.timeout),
57
+ signal: options.signal,
58
+ });
59
+ this.streams.add(stream);
60
+ return stream;
61
+ }
62
+ async close() {
63
+ try {
64
+ const results = await Promise.allSettled([...this.streams].map((stream) => stream.cancel()));
65
+ for (const result of results)
66
+ if (result.status === "rejected")
67
+ throw result.reason;
68
+ }
69
+ finally {
70
+ this.connection?.close();
71
+ }
72
+ }
73
+ }
@@ -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: 262144,
9
+ transcriptBytes: 65536,
10
+ queuedUpdates: 16,
11
+ };
@@ -0,0 +1,13 @@
1
+ import * as schema from "@rimelabs/api";
2
+ import type { TranscriptionPartial, TranscriptionFinal } from "./types.js";
3
+ /** Validate the wire sequence before exposing any final result. */
4
+ export declare class TranscriptState {
5
+ private readonly textLimit;
6
+ language: string | null;
7
+ private revision;
8
+ private text;
9
+ private final;
10
+ constructor(textLimit: number);
11
+ accept(message: schema.StreamingTranscriptionResponse, inputDone: boolean): TranscriptionPartial | null;
12
+ finish(): TranscriptionFinal;
13
+ }
@@ -0,0 +1,61 @@
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
+ language = null;
7
+ revision = 0n;
8
+ text = "";
9
+ final = null;
10
+ constructor(textLimit) {
11
+ this.textLimit = textLimit;
12
+ }
13
+ accept(message, inputDone) {
14
+ const payload = message.payload;
15
+ if (payload.case === undefined)
16
+ return null;
17
+ if (this.final)
18
+ throw new RimeStreamError("Received a message after transcription completion");
19
+ if (payload.case === "accepted") {
20
+ const accepted = payload.value;
21
+ if (this.language !== null)
22
+ throw new RimeStreamError("Received duplicate transcription acceptance");
23
+ if (accepted.outputContract !==
24
+ schema.StreamingOutputContract.REVISED_HYPOTHESES)
25
+ throw new RimeStreamError("The service accepted a different transcript contract");
26
+ if (!accepted.language?.tag || !accepted.language.source)
27
+ throw new RimeStreamError("The service did not confirm a resolved language");
28
+ this.language = accepted.language.tag;
29
+ return null;
30
+ }
31
+ if (this.language === null)
32
+ throw new RimeStreamError("Received a transcript before acceptance");
33
+ if (payload.case !== "hypothesis" && payload.case !== "done")
34
+ throw new RimeStreamError("Received an unexpected transcription response");
35
+ const value = payload.value;
36
+ if (Buffer.byteLength(value.text, "utf8") > this.textLimit)
37
+ throw new RimeResourceLimitError("The transcript exceeds the SDK text limit");
38
+ if (payload.case === "hypothesis") {
39
+ if (value.revision <= this.revision ||
40
+ (this.revision === 0n && value.revision !== 1n))
41
+ throw new RimeStreamError("Transcript revisions are not increasing from one");
42
+ this.revision = value.revision;
43
+ this.text = value.text;
44
+ return { kind: "partial", text: value.text };
45
+ }
46
+ if (!inputDone)
47
+ throw new RimeStreamError("The service completed before input finished");
48
+ if (value.revision !== this.revision || value.text !== this.text)
49
+ throw new RimeStreamError("Final transcript does not match the last hypothesis");
50
+ const language = payload.value.language;
51
+ if (language?.tag !== this.language || !language.source)
52
+ throw new RimeStreamError("Final language does not match the accepted language");
53
+ this.final = { kind: "final", text: value.text, language: language.tag };
54
+ return null;
55
+ }
56
+ finish() {
57
+ if (!this.final)
58
+ throw new RimeStreamError("The service ended without a final transcript");
59
+ return this.final;
60
+ }
61
+ }
@@ -0,0 +1,16 @@
1
+ import type { TranscriptionUpdate } from "./types.js";
2
+ /** Bounded, atomic transcript events; failure wakes producers and consumers. */
3
+ export declare class TranscriptQueue {
4
+ private readonly limit;
5
+ private items;
6
+ private closed;
7
+ private error;
8
+ private waiters;
9
+ constructor(limit: number);
10
+ private wait;
11
+ private wake;
12
+ put(value: TranscriptionUpdate): Promise<void>;
13
+ get(): Promise<IteratorResult<TranscriptionUpdate>>;
14
+ finish(): void;
15
+ fail(error: Error): void;
16
+ }
@@ -0,0 +1,45 @@
1
+ /** Bounded, atomic transcript events; failure wakes producers and consumers. */
2
+ export class TranscriptQueue {
3
+ limit;
4
+ items = [];
5
+ closed = false;
6
+ error = null;
7
+ waiters = new Set();
8
+ constructor(limit) {
9
+ this.limit = limit;
10
+ }
11
+ wait() {
12
+ return new Promise((resolve) => this.waiters.add(resolve));
13
+ }
14
+ wake() {
15
+ for (const resolve of this.waiters)
16
+ resolve();
17
+ this.waiters.clear();
18
+ }
19
+ async put(value) {
20
+ while (!this.closed && this.items.length >= this.limit)
21
+ await this.wait();
22
+ if (!this.closed) {
23
+ this.items.push(value);
24
+ this.wake();
25
+ }
26
+ }
27
+ async get() {
28
+ while (!this.closed && !this.items.length)
29
+ await this.wait();
30
+ if (this.error)
31
+ throw this.error;
32
+ const value = this.items.shift();
33
+ this.wake();
34
+ return value ? { done: false, value } : { done: true, value: undefined };
35
+ }
36
+ finish() {
37
+ this.closed = true;
38
+ this.wake();
39
+ }
40
+ fail(error) {
41
+ this.error ??= error;
42
+ this.items = [];
43
+ this.finish();
44
+ }
45
+ }
@@ -0,0 +1,48 @@
1
+ import type * as grpc from "@grpc/grpc-js";
2
+ import type { AudioSource, TranscriptionOptions, TranscriptionUpdate } from "./types.js";
3
+ interface StreamOwner {
4
+ prepare(signal: AbortSignal): Promise<{
5
+ client: grpc.Client;
6
+ metadata: grpc.Metadata;
7
+ }>;
8
+ checkOpen(): void;
9
+ forget(stream: TranscriptStream): void;
10
+ }
11
+ /** Lazy, single-consumer stream. Source exhaustion commits input; return/cancel aborts it. */
12
+ export declare class TranscriptStream implements AsyncIterableIterator<TranscriptionUpdate> {
13
+ private readonly owner;
14
+ private source;
15
+ private readonly options;
16
+ private readonly controller;
17
+ private readonly queue;
18
+ private readonly timers;
19
+ private worker;
20
+ private workerFinished;
21
+ private call;
22
+ private failure;
23
+ private reading;
24
+ private finished;
25
+ private inputDone;
26
+ private accepted;
27
+ private readonly acceptance;
28
+ private admission;
29
+ private completion;
30
+ private readonly externalAbort;
31
+ constructor(owner: StreamOwner, source: AudioSource | null, options: TranscriptionOptions);
32
+ /** Server request identity, available once response headers arrive. */
33
+ get requestId(): string | null;
34
+ private deadline;
35
+ private start;
36
+ private fail;
37
+ private produce;
38
+ private read;
39
+ private run;
40
+ [Symbol.asyncIterator](): this;
41
+ next(): Promise<IteratorResult<TranscriptionUpdate>>;
42
+ private cleanup;
43
+ /** Cancel unfinished work and release owned resources; safe to repeat. */
44
+ cancel(): Promise<void>;
45
+ return(): Promise<IteratorResult<TranscriptionUpdate>>;
46
+ [Symbol.asyncDispose](): Promise<void>;
47
+ }
48
+ export {};