@rimelabs/sdk 0.1.0-alpha.25 → 0.1.0-alpha.26

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.26](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.25...typescript-v0.1.0-alpha.26) (2026-10-30)
4
+
5
+
6
+ ### Features
7
+
8
+ * expose realtime connection diagnostics across SDKs ([#116](https://github.com/rimelabs/rime-sdk/issues/116)) ([da50cf4](https://github.com/rimelabs/rime-sdk/commit/da50cf41b0ca4631c0c71e48ef31cd207ab5e713))
9
+
3
10
  ## [0.1.0-alpha.25](https://github.com/rimelabs/rime-sdk/compare/typescript-v0.1.0-alpha.24...typescript-v0.1.0-alpha.25) (2026-10-28)
4
11
 
5
12
 
package/dist/errors.d.ts CHANGED
@@ -1,6 +1,18 @@
1
+ /** Optional connection diagnostics. These fields do not initiate retries. */
2
+ export interface RimeErrorOptions extends ErrorOptions {
3
+ /** Refused HTTP upgrade status, such as 503; null when unavailable. */
4
+ statusCode?: number | null;
5
+ /** Received WebSocket close code, such as 1013; null when unavailable. */
6
+ closeCode?: number | null;
7
+ /** Server-requested delay in seconds. Zero is valid; null means unavailable. */
8
+ retryAfterSeconds?: number | null;
9
+ }
1
10
  export declare class RimeError extends Error {
2
11
  readonly requestId: string | null;
3
- constructor(message: string, requestId?: string | null, options?: ErrorOptions);
12
+ readonly statusCode: number | null;
13
+ readonly closeCode: number | null;
14
+ readonly retryAfterSeconds: number | null;
15
+ constructor(message: string, requestId?: string | null, options?: RimeErrorOptions);
4
16
  }
5
17
  export declare class RimeAuthenticationError extends RimeError {
6
18
  }
package/dist/errors.js CHANGED
@@ -1,9 +1,15 @@
1
1
  export class RimeError extends Error {
2
2
  requestId;
3
+ statusCode;
4
+ closeCode;
5
+ retryAfterSeconds;
3
6
  constructor(message, requestId = null, options) {
4
7
  super(message, options);
5
8
  this.name = new.target.name;
6
9
  this.requestId = requestId;
10
+ this.statusCode = options?.statusCode ?? null;
11
+ this.closeCode = options?.closeCode ?? null;
12
+ this.retryAfterSeconds = options?.retryAfterSeconds ?? null;
7
13
  }
8
14
  }
9
15
  export class RimeAuthenticationError extends RimeError {
@@ -1,7 +1,83 @@
1
1
  import WebSocket from "ws";
2
2
  import { RimeAuthenticationError, RimeResourceLimitError, RimeStreamError, RimeUnavailableError, } from "../errors.js";
3
3
  import { Deferred } from "./async.js";
4
- function handshakeError(status, body, requestId) {
4
+ const months = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" ");
5
+ const dayName = "(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)";
6
+ const monthName = `(?:${months.join("|")})`;
7
+ const clock = "([0-9]{2}):([0-9]{2}):([0-9]{2})";
8
+ const imfDate = new RegExp(`^${dayName}, ([0-9]{2}) (${monthName}) ([0-9]{4}) ${clock} GMT$`);
9
+ const rfc850Date = new RegExp(`^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), ([0-9]{2})-(${monthName})-([0-9]{2}) ${clock} GMT$`);
10
+ const asctimeDate = new RegExp(`^${dayName} (${monthName}) ([0-9]{2}| [0-9]) ${clock} ([0-9]{4})$`);
11
+ function httpDate(value, now) {
12
+ let parts = imfDate.exec(value);
13
+ let shortYear = false;
14
+ if (!parts) {
15
+ parts = rfc850Date.exec(value);
16
+ shortYear = parts !== null;
17
+ }
18
+ if (!parts) {
19
+ const legacy = asctimeDate.exec(value);
20
+ if (!legacy)
21
+ return null;
22
+ parts = [
23
+ legacy[0],
24
+ legacy[2],
25
+ legacy[1],
26
+ legacy[6],
27
+ legacy[3],
28
+ legacy[4],
29
+ legacy[5],
30
+ ];
31
+ }
32
+ const day = Number(parts[1]);
33
+ const month = months.indexOf(parts[2]);
34
+ let year = Number(parts[3]);
35
+ const hour = Number(parts[4]);
36
+ const minute = Number(parts[5]);
37
+ const second = Number(parts[6]);
38
+ if (shortYear) {
39
+ const current = new Date(now);
40
+ const limit = [
41
+ current.getUTCFullYear() + 50,
42
+ current.getUTCMonth(),
43
+ current.getUTCDate(),
44
+ current.getUTCHours(),
45
+ current.getUTCMinutes(),
46
+ current.getUTCSeconds(),
47
+ ];
48
+ year += Math.floor(limit[0] / 100) * 100;
49
+ const candidate = [year, month, day, hour, minute, second];
50
+ const difference = candidate.findIndex((part, index) => part !== limit[index]);
51
+ if (difference !== -1 && candidate[difference] > limit[difference])
52
+ year -= 100;
53
+ }
54
+ // Use setters so years 0000–0099 aren't silently changed to 1900–1999.
55
+ const date = new Date(0);
56
+ date.setUTCFullYear(year, month, day);
57
+ date.setUTCHours(hour, minute, second, 0);
58
+ if (year < 1 ||
59
+ date.getUTCFullYear() !== year ||
60
+ date.getUTCMonth() !== month ||
61
+ date.getUTCDate() !== day ||
62
+ date.getUTCHours() !== hour ||
63
+ date.getUTCMinutes() !== minute ||
64
+ date.getUTCSeconds() !== second)
65
+ return null;
66
+ return date.getTime();
67
+ }
68
+ function retryAfter(values) {
69
+ if (values?.length !== 1 || values[0].length > 256)
70
+ return null;
71
+ const value = values[0].trim();
72
+ if (/^[0-9]+$/.test(value)) {
73
+ const seconds = Number(value);
74
+ return Number.isFinite(seconds) ? seconds : null;
75
+ }
76
+ const now = Date.now();
77
+ const date = httpDate(value, now);
78
+ return date === null ? null : Math.max(0, (date - now) / 1000);
79
+ }
80
+ function handshakeError(status, body, requestId, retryAfterSeconds) {
5
81
  let errorType = RimeUnavailableError;
6
82
  let reason = "Realtime endpoint rejected the connection";
7
83
  if ([401, 403].includes(status)) {
@@ -35,7 +111,10 @@ function handshakeError(status, body, requestId) {
35
111
  // Invalid response bodies do not change the HTTP error category.
36
112
  }
37
113
  }
38
- return new errorType(message, requestId);
114
+ return new errorType(message, requestId, {
115
+ statusCode: status,
116
+ retryAfterSeconds,
117
+ });
39
118
  }
40
119
  /** Private transport. The public declarations do not require ws types. */
41
120
  export class RealtimeConnection {
@@ -74,7 +153,7 @@ export class RealtimeConnection {
74
153
  clearTimeout(timer);
75
154
  this.refusal = undefined;
76
155
  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));
156
+ this.failure(handshakeError(response.statusCode ?? 0, complete ? Buffer.concat(chunks) : Buffer.alloc(0), typeof id === "string" ? id : null, retryAfter(response.headersDistinct["retry-after"])));
78
157
  response.destroy();
79
158
  request.destroy();
80
159
  this.socket.terminate();
@@ -104,10 +183,10 @@ export class RealtimeConnection {
104
183
  }
105
184
  this.onMessage(data.toString());
106
185
  });
107
- this.socket.on("close", () => {
186
+ this.socket.on("close", (code) => {
108
187
  this.refusal?.();
109
188
  this.ended.resolve();
110
- this.failure(new RimeStreamError("Realtime connection closed; reconnect is not automatic"));
189
+ this.failure(new RimeStreamError("Realtime connection closed; reconnect is not automatic", null, { closeCode: code === 1005 || code === 1006 ? null : code }));
111
190
  });
112
191
  }
113
192
  failure(error) {
package/docs/realtime.md CHANGED
@@ -452,6 +452,20 @@ The voice examples report actual playback.
452
452
 
453
453
  ## Timeouts and failures
454
454
 
455
+ `RimeError` exposes `statusCode` for a refused HTTP upgrade, `closeCode` for a
456
+ received WebSocket close code, and `retryAfterSeconds` for the server's requested
457
+ delay. For example, a busy endpoint can return `statusCode=503` and
458
+ `retryAfterSeconds=2`. Missing values are `null`; zero is a valid delay. The
459
+ existing `requestId` retains the server's request ID on an HTTP refusal.
460
+ Invalid or duplicate `Retry-After` headers are ignored. HTTP dates become a delay
461
+ relative to the client clock, with past dates becoming zero.
462
+
463
+ Error categories stay the same. A WebSocket closure uses `RimeStreamError`;
464
+ no received close code means `closeCode=null`, including a lost connection or
465
+ an empty close frame. These fields require a release with structured connection
466
+ diagnostics. The SDK does not retry startup automatically. These fields do not
467
+ make replay of an accepted request safe.
468
+
455
469
  Configure positive, finite seconds through `timeouts`. `connectS` bounds the
456
470
  WebSocket handshake and initial session event. `readyS` bounds turn admission,
457
471
  tool readiness, and waiting for generation to end before a playback report.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelabs/sdk",
3
- "version": "0.1.0-alpha.25",
3
+ "version": "0.1.0-alpha.26",
4
4
  "description": "Rime streaming TTS, STT and Prism realtime SDK for Node.js",
5
5
  "license": "MIT",
6
6
  "type": "module",