@rimelabs/sdk 0.1.0-alpha.22 → 0.1.0-alpha.23

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.23](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.22...typescript-v0.1.0-alpha.23) (2026-10-27)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * preserve Prism handshake error categories and diagnostics ([#110](https://github.com/rimelabs/rime-sdk/issues/110)) ([75568b2](https://github.com/rimelabs/rime-sdk/commit/75568b278afddd265a0a4f2c26c716ccb6b29514))
9
+
3
10
  ## [0.1.0-alpha.22](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.21...typescript-v0.1.0-alpha.22) (2026-10-24)
4
11
 
5
12
 
package/README.md CHANGED
@@ -28,6 +28,12 @@ STT use the SDK and Node.js built-ins.
28
28
 
29
29
  ## Prism: send recorded speech
30
30
 
31
+ If Prism refuses the connection with HTTP 429, `realtime.connect()` throws
32
+ `RimeResourceLimitError`. HTTP 503 throws `RimeUnavailableError`. Handshake
33
+ errors include the HTTP status, the server request ID when supplied, and a
34
+ short server explanation when available. Missing or invalid error bodies use
35
+ a default message. The SDK does not retry the connection automatically.
36
+
31
37
  With `RIME_API_KEY` set, the SDK connects to
32
38
  `wss://prism.api.rime.ai/v1/realtime` and uses the server's default voice.
33
39
  Pass `endpoint` or `voice` to select another deployment or voice.
@@ -6,6 +6,8 @@ export declare class RealtimeConnection {
6
6
  private readonly ended;
7
7
  private closing;
8
8
  private connected;
9
+ private failed;
10
+ private refusal;
9
11
  readonly ready: Promise<void>;
10
12
  onMessage: (raw: string) => void;
11
13
  onFailure: (error: RimeError) => void;
@@ -1,6 +1,42 @@
1
1
  import WebSocket from "ws";
2
- import { RimeAuthenticationError, RimeStreamError, RimeUnavailableError, } from "../errors.js";
2
+ import { RimeAuthenticationError, RimeResourceLimitError, RimeStreamError, RimeUnavailableError, } from "../errors.js";
3
3
  import { Deferred } from "./async.js";
4
+ function handshakeError(status, body, requestId) {
5
+ let errorType = RimeUnavailableError;
6
+ let reason = "Realtime endpoint rejected the connection";
7
+ if ([401, 403].includes(status)) {
8
+ errorType = RimeAuthenticationError;
9
+ reason = "Realtime endpoint rejected credentials";
10
+ }
11
+ else if (status === 429) {
12
+ errorType = RimeResourceLimitError;
13
+ reason = "Realtime connection or rate limit reached";
14
+ }
15
+ else if (status === 503) {
16
+ reason = "Realtime endpoint is unavailable";
17
+ }
18
+ let message = `${reason} (HTTP ${status})`;
19
+ if (body.length > 0 && body.length < 1024) {
20
+ try {
21
+ let detail = new TextDecoder("utf-8", { fatal: true })
22
+ .decode(body)
23
+ .trim();
24
+ if (typeof detail === "string" && /^[{[]/.test(detail))
25
+ detail = JSON.parse(detail)?.error?.message;
26
+ if (typeof detail === "string" &&
27
+ !detail.trimStart().startsWith("<") &&
28
+ !/[\x00-\x08\x0b\x0c\x0e-\x1f]/.test(detail)) {
29
+ detail = detail.trim().replace(/\s+/gu, " ");
30
+ if (detail)
31
+ message += `: ${detail}`;
32
+ }
33
+ }
34
+ catch {
35
+ // Invalid response bodies do not change the HTTP error category.
36
+ }
37
+ }
38
+ return new errorType(message, requestId);
39
+ }
4
40
  /** Private transport. The public declarations do not require ws types. */
5
41
  export class RealtimeConnection {
6
42
  socket;
@@ -8,6 +44,8 @@ export class RealtimeConnection {
8
44
  ended = new Deferred();
9
45
  closing = null;
10
46
  connected = false;
47
+ failed = false;
48
+ refusal;
11
49
  ready = this.opened.promise;
12
50
  onMessage = () => { };
13
51
  onFailure = () => { };
@@ -26,17 +64,39 @@ export class RealtimeConnection {
26
64
  this.opened.resolve();
27
65
  });
28
66
  this.socket.on("unexpected-response", (request, response) => {
29
- const error = [401, 403].includes(response.statusCode ?? 0)
30
- ? new RimeAuthenticationError("Realtime endpoint rejected credentials")
31
- : new RimeUnavailableError("Realtime endpoint rejected the connection");
32
- this.failure(error);
33
- response.destroy();
34
- request.destroy();
35
- this.socket.terminate();
67
+ const chunks = [];
68
+ let size = 0;
69
+ let finished = false;
70
+ const finish = (complete = false) => {
71
+ if (finished)
72
+ return;
73
+ finished = true;
74
+ clearTimeout(timer);
75
+ this.refusal = undefined;
76
+ const id = response.headers["x-request-id"];
77
+ this.failure(handshakeError(response.statusCode ?? 0, complete ? Buffer.concat(chunks) : Buffer.alloc(0), typeof id === "string" ? id : null));
78
+ response.destroy();
79
+ request.destroy();
80
+ this.socket.terminate();
81
+ };
82
+ const timer = setTimeout(finish, 1000);
83
+ this.refusal = finish;
84
+ response.on("data", (chunk) => {
85
+ size += chunk.length;
86
+ if (size >= 1024)
87
+ finish();
88
+ else
89
+ chunks.push(chunk);
90
+ });
91
+ response.on("end", () => finish(true));
92
+ response.on("error", () => finish());
93
+ response.on("aborted", () => finish());
36
94
  });
37
- this.socket.on("error", () => this.failure(!this.connected
38
- ? new RimeUnavailableError("Realtime handshake failed")
39
- : new RimeStreamError("Invalid event or lost realtime connection")));
95
+ this.socket.on("error", () => this.refusal
96
+ ? this.refusal()
97
+ : this.failure(!this.connected
98
+ ? new RimeUnavailableError("Realtime handshake failed")
99
+ : new RimeStreamError("Invalid event or lost realtime connection")));
40
100
  this.socket.on("message", (data, binary) => {
41
101
  if (binary) {
42
102
  this.failure(new RimeStreamError("Expected a JSON text event"));
@@ -45,11 +105,15 @@ export class RealtimeConnection {
45
105
  this.onMessage(data.toString());
46
106
  });
47
107
  this.socket.on("close", () => {
108
+ this.refusal?.();
48
109
  this.ended.resolve();
49
110
  this.failure(new RimeStreamError("Realtime connection closed; reconnect is not automatic"));
50
111
  });
51
112
  }
52
113
  failure(error) {
114
+ if (this.failed)
115
+ return;
116
+ this.failed = true;
53
117
  this.opened.reject(error);
54
118
  this.onFailure(error);
55
119
  }
@@ -66,6 +130,7 @@ export class RealtimeConnection {
66
130
  close() {
67
131
  if (!this.closing) {
68
132
  this.closing = (async () => {
133
+ this.refusal?.();
69
134
  if (this.socket.readyState === WebSocket.CLOSED)
70
135
  return;
71
136
  const timer = setTimeout(() => this.socket.terminate(), 2000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelabs/sdk",
3
- "version": "0.1.0-alpha.22",
3
+ "version": "0.1.0-alpha.23",
4
4
  "description": "Rime streaming TTS, STT and Prism realtime SDK for Node.js",
5
5
  "license": "MIT",
6
6
  "type": "module",