@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 +7 -0
- package/README.md +6 -0
- package/dist/realtime/transport.d.ts +2 -0
- package/dist/realtime/transport.js +76 -11
- package/package.json +1 -1
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.
|
|
@@ -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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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.
|
|
38
|
-
?
|
|
39
|
-
:
|
|
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);
|