livekit-plugin-parrot-stt 0.1.0
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/README.md +79 -0
- package/dist/index.cjs +303 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +89 -0
- package/dist/index.d.ts +89 -0
- package/dist/index.js +280 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
- package/src/index.ts +18 -0
- package/src/models.ts +121 -0
- package/src/stt.ts +280 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# livekit-plugin-parrot-stt
|
|
2
|
+
|
|
3
|
+
[Ringg Parrot STT](https://www.ringg.ai/models/speech-to-text/v1) plugin for
|
|
4
|
+
[LiveKit Agents](https://docs.livekit.io/agents/) (Node.js / TypeScript).
|
|
5
|
+
|
|
6
|
+
It implements LiveKit's streaming STT interface (`stt.STT` + `stt.SpeechStream`)
|
|
7
|
+
by speaking Ringg's WebSocket protocol directly, so it drops straight into a
|
|
8
|
+
`voice.AgentSession` like any first-party plugin.
|
|
9
|
+
|
|
10
|
+
> Ringg's official SDK (`ringglabs`) is Python-only. This plugin re-implements the
|
|
11
|
+
> same wire protocol in TypeScript.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install livekit-plugin-parrot-stt
|
|
17
|
+
# peer deps (provided by your agent app):
|
|
18
|
+
npm install @livekit/agents @livekit/rtc-node
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { voice } from '@livekit/agents';
|
|
25
|
+
import * as ringg from 'livekit-plugin-parrot-stt';
|
|
26
|
+
|
|
27
|
+
const session = new voice.AgentSession({
|
|
28
|
+
stt: new ringg.STT({
|
|
29
|
+
// reads RINGG_STT_URL / RINGG_STT_API_KEY from the environment by default
|
|
30
|
+
language: 'en', // 'hi' | 'en'
|
|
31
|
+
}),
|
|
32
|
+
// llm, tts, vad, ...
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Standalone (no `AgentSession`):
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import * as ringg from 'livekit-plugin-parrot-stt';
|
|
40
|
+
|
|
41
|
+
const stt = new ringg.STT({ url: 'wss://.../stt/v1/stream', apiKey: '...', language: 'en' });
|
|
42
|
+
const stream = stt.stream();
|
|
43
|
+
|
|
44
|
+
// push AudioFrames (Int16 PCM), then close the input:
|
|
45
|
+
for (const frame of frames) stream.pushFrame(frame);
|
|
46
|
+
stream.endInput();
|
|
47
|
+
|
|
48
|
+
for await (const ev of stream) {
|
|
49
|
+
if (ev.type === /* FINAL_TRANSCRIPT */ 2) console.log(ev.alternatives?.[0]?.text);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Options
|
|
54
|
+
|
|
55
|
+
| Option | Env | Default | Notes |
|
|
56
|
+
| --- | --- | --- | --- |
|
|
57
|
+
| `url` | `RINGG_STT_URL` | `wss://prod-api.ringg.ai/stt/v1/stream` | Full WS URL. Confirm exact path with Ringg. |
|
|
58
|
+
| `apiKey` | `RINGG_STT_API_KEY` | — | Sent inside the `start` JSON frame (not a header). |
|
|
59
|
+
| `language` | `RINGG_STT_LANGUAGE` | `en` | `hi` \| `en` \| … |
|
|
60
|
+
| `sampleRate` | `RINGG_STT_SAMPLE_RATE` | `16000` | LiveKit audio is resampled to this. |
|
|
61
|
+
| `encoding` | `RINGG_STT_ENCODING` | `int16` | `int16` \| `linear16` \| `float32` \| `int32` |
|
|
62
|
+
| `mode` | `RINGG_STT_MODE` | `stream` | `stream` = interim+final; `on_final` = finals only. |
|
|
63
|
+
| `enableCapPunc` | — | `true` | Request capitalisation/punctuation. |
|
|
64
|
+
| `vadTailSilMs` | — | `200` | Server-VAD trailing silence. |
|
|
65
|
+
| `vadConfidence` | — | `0.55` | Server-VAD threshold. |
|
|
66
|
+
| `keepaliveIntervalMs` | — | `30000` | JSON ping keepalive (0 disables). |
|
|
67
|
+
|
|
68
|
+
## How it maps to LiveKit
|
|
69
|
+
|
|
70
|
+
- Connect → send JSON `start` (auth = `api_key`) → wait for `ready`.
|
|
71
|
+
- Stream raw **binary PCM16 LE mono** frames (20 ms chunks).
|
|
72
|
+
- Ringg `transcript` events → `INTERIM_TRANSCRIPT` (`is_final:false`) / `FINAL_TRANSCRIPT` (`is_final:true`).
|
|
73
|
+
- `START_OF_SPEECH` / `END_OF_SPEECH` are synthesised around transcript activity.
|
|
74
|
+
- Endpointing uses Ringg's **server-side VAD** (`mode:"stream"`), which is the
|
|
75
|
+
clean analog of the Pipecat integration through LiveKit's audio-only STT interface.
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
Apache-2.0
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
STT: () => STT,
|
|
24
|
+
SpeechStream: () => SpeechStream,
|
|
25
|
+
buildStartPayload: () => buildStartPayload,
|
|
26
|
+
defaultRinggSTTOptions: () => defaultRinggSTTOptions
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
var import_agents2 = require("@livekit/agents");
|
|
30
|
+
|
|
31
|
+
// src/stt.ts
|
|
32
|
+
var import_agents = require("@livekit/agents");
|
|
33
|
+
var import_ws = require("ws");
|
|
34
|
+
|
|
35
|
+
// src/models.ts
|
|
36
|
+
var envInt = (name, fallback) => {
|
|
37
|
+
const v = process.env[name];
|
|
38
|
+
if (v === void 0 || v === "") return fallback;
|
|
39
|
+
const n = Number(v);
|
|
40
|
+
return Number.isFinite(n) ? n : fallback;
|
|
41
|
+
};
|
|
42
|
+
var defaultRinggSTTOptions = {
|
|
43
|
+
url: process.env.RINGG_STT_URL ?? "wss://prod-api.ringg.ai/stt/v1/stream",
|
|
44
|
+
apiKey: process.env.RINGG_STT_API_KEY ?? "",
|
|
45
|
+
language: process.env.RINGG_STT_LANGUAGE ?? "en",
|
|
46
|
+
sampleRate: envInt("RINGG_STT_SAMPLE_RATE", 16e3),
|
|
47
|
+
encoding: process.env.RINGG_STT_ENCODING ?? "int16",
|
|
48
|
+
mode: process.env.RINGG_STT_MODE ?? "stream",
|
|
49
|
+
enableCapPunc: true,
|
|
50
|
+
vadTailSilMs: 200,
|
|
51
|
+
vadConfidence: 0.55,
|
|
52
|
+
acceptClientVadEvents: false,
|
|
53
|
+
keepaliveIntervalMs: 3e4,
|
|
54
|
+
finalizeGraceMs: 1500
|
|
55
|
+
};
|
|
56
|
+
var buildStartPayload = (opts) => ({
|
|
57
|
+
type: "start",
|
|
58
|
+
sample_rate: opts.sampleRate,
|
|
59
|
+
encoding: opts.encoding,
|
|
60
|
+
language: opts.language,
|
|
61
|
+
mode: opts.mode,
|
|
62
|
+
vad_tail_sil_ms: opts.vadTailSilMs,
|
|
63
|
+
vad_confidence: opts.vadConfidence,
|
|
64
|
+
enable_cap_punc: opts.enableCapPunc,
|
|
65
|
+
accept_client_vad_events: opts.acceptClientVadEvents,
|
|
66
|
+
api_key: opts.apiKey
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// src/stt.ts
|
|
70
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
71
|
+
var STT = class extends import_agents.stt.STT {
|
|
72
|
+
label = "ringg.STT";
|
|
73
|
+
#opts;
|
|
74
|
+
constructor(opts = {}) {
|
|
75
|
+
super({ streaming: true, interimResults: true });
|
|
76
|
+
this.#opts = { ...defaultRinggSTTOptions, ...opts };
|
|
77
|
+
if (!this.#opts.apiKey) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"Ringg STT: API key is required \u2014 set $RINGG_STT_API_KEY or pass { apiKey }."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (!this.#opts.url) {
|
|
83
|
+
throw new Error("Ringg STT: url is required \u2014 set $RINGG_STT_URL or pass { url }.");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
get provider() {
|
|
87
|
+
return "ringg";
|
|
88
|
+
}
|
|
89
|
+
get model() {
|
|
90
|
+
return `ringg-parrot-${this.#opts.language}`;
|
|
91
|
+
}
|
|
92
|
+
updateOptions(opts) {
|
|
93
|
+
this.#opts = { ...this.#opts, ...opts };
|
|
94
|
+
}
|
|
95
|
+
async _recognize(_frame) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"Ringg STT plugin is streaming-only; use stream(). (Ringg also exposes a REST /transcriptions endpoint, but it is not wired into this plugin.)"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
stream(options) {
|
|
101
|
+
return new SpeechStream(this, this.#opts, options?.connOptions);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var SpeechStream = class _SpeechStream extends import_agents.stt.SpeechStream {
|
|
105
|
+
label = "ringg.SpeechStream";
|
|
106
|
+
#opts;
|
|
107
|
+
#logger = (0, import_agents.log)();
|
|
108
|
+
#speaking = false;
|
|
109
|
+
#requestId;
|
|
110
|
+
constructor(sttImpl, opts, connOptions) {
|
|
111
|
+
super(sttImpl, opts.sampleRate, connOptions);
|
|
112
|
+
this.#opts = opts;
|
|
113
|
+
}
|
|
114
|
+
async run() {
|
|
115
|
+
const ws = new import_ws.WebSocket(this.#opts.url);
|
|
116
|
+
ws.binaryType = "nodebuffer";
|
|
117
|
+
await new Promise((resolve, reject) => {
|
|
118
|
+
const onErr = (err) => reject(err);
|
|
119
|
+
ws.once("open", () => {
|
|
120
|
+
ws.off("error", onErr);
|
|
121
|
+
resolve();
|
|
122
|
+
});
|
|
123
|
+
ws.once("error", onErr);
|
|
124
|
+
});
|
|
125
|
+
ws.send(JSON.stringify(buildStartPayload(this.#opts)));
|
|
126
|
+
await new Promise((resolve, reject) => {
|
|
127
|
+
const onMsg = (data, isBinary) => {
|
|
128
|
+
if (isBinary) return;
|
|
129
|
+
let ev;
|
|
130
|
+
try {
|
|
131
|
+
ev = JSON.parse(data.toString());
|
|
132
|
+
} catch {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (ev.type === "ready") {
|
|
136
|
+
this.#requestId = ev.request_id;
|
|
137
|
+
cleanup();
|
|
138
|
+
resolve();
|
|
139
|
+
} else if (ev.type === "error") {
|
|
140
|
+
cleanup();
|
|
141
|
+
reject(
|
|
142
|
+
new Error(
|
|
143
|
+
`Ringg STT: handshake rejected \u2014 ${ev.detail ?? "unknown error"}`
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
const onClose = (code) => {
|
|
149
|
+
cleanup();
|
|
150
|
+
reject(new Error(`Ringg STT: connection closed during handshake (code ${code})`));
|
|
151
|
+
};
|
|
152
|
+
const cleanup = () => {
|
|
153
|
+
ws.off("message", onMsg);
|
|
154
|
+
ws.off("close", onClose);
|
|
155
|
+
};
|
|
156
|
+
ws.on("message", onMsg);
|
|
157
|
+
ws.once("close", onClose);
|
|
158
|
+
});
|
|
159
|
+
this.#logger.child({ url: this.#opts.url, language: this.#opts.language, requestId: this.#requestId }).debug("ringg stt: connected");
|
|
160
|
+
let closed = false;
|
|
161
|
+
const closeWs = () => {
|
|
162
|
+
if (!closed) {
|
|
163
|
+
closed = true;
|
|
164
|
+
try {
|
|
165
|
+
ws.close();
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
let keepalive;
|
|
171
|
+
if (this.#opts.keepaliveIntervalMs > 0) {
|
|
172
|
+
keepalive = setInterval(() => {
|
|
173
|
+
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
174
|
+
try {
|
|
175
|
+
ws.send(JSON.stringify({ type: "ping" }));
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}, this.#opts.keepaliveIntervalMs);
|
|
180
|
+
}
|
|
181
|
+
const recvTask = new Promise((resolve, reject) => {
|
|
182
|
+
ws.on("message", (data, isBinary) => {
|
|
183
|
+
if (isBinary) return;
|
|
184
|
+
let ev;
|
|
185
|
+
try {
|
|
186
|
+
ev = JSON.parse(data.toString());
|
|
187
|
+
} catch {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
this.#handleEvent(ev);
|
|
191
|
+
});
|
|
192
|
+
ws.once("close", () => resolve());
|
|
193
|
+
ws.once("error", (err) => reject(err));
|
|
194
|
+
});
|
|
195
|
+
const sendTask = async () => {
|
|
196
|
+
const samples20ms = Math.floor(this.#opts.sampleRate / 50);
|
|
197
|
+
const bstream = new import_agents.AudioByteStream(this.#opts.sampleRate, 1, samples20ms);
|
|
198
|
+
const shipFrames = (frames) => {
|
|
199
|
+
for (const f of frames) {
|
|
200
|
+
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
201
|
+
ws.send(new Uint8Array(f.data.buffer, f.data.byteOffset, f.data.byteLength));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
for (; ; ) {
|
|
206
|
+
if (closed) break;
|
|
207
|
+
const { done, value } = await this.input.next();
|
|
208
|
+
if (done) break;
|
|
209
|
+
if (value === _SpeechStream.FLUSH_SENTINEL) {
|
|
210
|
+
shipFrames(bstream.flush());
|
|
211
|
+
} else {
|
|
212
|
+
shipFrames(bstream.write(value.data.buffer));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
shipFrames(bstream.flush());
|
|
216
|
+
if (ws.readyState === import_ws.WebSocket.OPEN) {
|
|
217
|
+
ws.send(JSON.stringify({ type: "end" }));
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const abortPromise = new Promise((resolve) => {
|
|
221
|
+
const signal = this.abortController.signal;
|
|
222
|
+
if (signal.aborted) return resolve();
|
|
223
|
+
signal.addEventListener("abort", () => resolve(), { once: true });
|
|
224
|
+
});
|
|
225
|
+
const finiteInputPath = (async () => {
|
|
226
|
+
await sendTask();
|
|
227
|
+
await sleep(this.#opts.finalizeGraceMs);
|
|
228
|
+
})();
|
|
229
|
+
try {
|
|
230
|
+
await Promise.race([abortPromise, recvTask, finiteInputPath]);
|
|
231
|
+
} finally {
|
|
232
|
+
if (keepalive) clearInterval(keepalive);
|
|
233
|
+
closeWs();
|
|
234
|
+
await recvTask.catch(() => {
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
#handleEvent(ev) {
|
|
239
|
+
switch (ev.type) {
|
|
240
|
+
case "transcript": {
|
|
241
|
+
const t = ev;
|
|
242
|
+
const text = (t.transcription ?? "").trim();
|
|
243
|
+
if (!text) return;
|
|
244
|
+
if (!this.#speaking) {
|
|
245
|
+
this.#speaking = true;
|
|
246
|
+
this.queue.put({ type: import_agents.stt.SpeechEventType.START_OF_SPEECH });
|
|
247
|
+
}
|
|
248
|
+
const data = {
|
|
249
|
+
language: (0, import_agents.asLanguageCode)(t.language ?? this.#opts.language),
|
|
250
|
+
text,
|
|
251
|
+
startTime: 0,
|
|
252
|
+
endTime: 0,
|
|
253
|
+
confidence: 1
|
|
254
|
+
};
|
|
255
|
+
if (t.is_final) {
|
|
256
|
+
this.queue.put({
|
|
257
|
+
type: import_agents.stt.SpeechEventType.FINAL_TRANSCRIPT,
|
|
258
|
+
alternatives: [data],
|
|
259
|
+
requestId: this.#requestId
|
|
260
|
+
});
|
|
261
|
+
if (this.#speaking) {
|
|
262
|
+
this.#speaking = false;
|
|
263
|
+
this.queue.put({ type: import_agents.stt.SpeechEventType.END_OF_SPEECH });
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
this.queue.put({
|
|
267
|
+
type: import_agents.stt.SpeechEventType.INTERIM_TRANSCRIPT,
|
|
268
|
+
alternatives: [data],
|
|
269
|
+
requestId: this.#requestId
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
case "error": {
|
|
275
|
+
this.#logger.child({ event: ev }).error("ringg stt: server error");
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
// ready (duplicate), ack, pong, and unknown types are ignored
|
|
279
|
+
default:
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// src/index.ts
|
|
286
|
+
var RinggPlugin = class extends import_agents2.Plugin {
|
|
287
|
+
constructor() {
|
|
288
|
+
super({
|
|
289
|
+
title: "ringg",
|
|
290
|
+
version: "0.1.0",
|
|
291
|
+
package: "livekit-plugin-parrot-stt"
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
import_agents2.Plugin.registerPlugin(new RinggPlugin());
|
|
296
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
297
|
+
0 && (module.exports = {
|
|
298
|
+
STT,
|
|
299
|
+
SpeechStream,
|
|
300
|
+
buildStartPayload,
|
|
301
|
+
defaultRinggSTTOptions
|
|
302
|
+
});
|
|
303
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/stt.ts","../src/models.ts"],"sourcesContent":["// livekit-plugin-parrot-stt — Ringg Parrot STT for LiveKit Agents (Node.js)\n\nimport { Plugin } from '@livekit/agents';\n\nexport * from './stt.js';\nexport * from './models.js';\n\nclass RinggPlugin extends Plugin {\n constructor() {\n super({\n title: 'ringg',\n version: '0.1.0',\n package: 'livekit-plugin-parrot-stt',\n });\n }\n}\n\nPlugin.registerPlugin(new RinggPlugin());\n","// Ringg Parrot STT plugin for LiveKit Agents.\n//\n// Implements the streaming STT interface (`stt.STT` + `stt.SpeechStream`) by\n// speaking the Ringg WebSocket protocol directly (there is no JS SDK).\n\nimport {\n type APIConnectOptions,\n type AudioBuffer,\n AudioByteStream,\n asLanguageCode,\n log,\n stt,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport { type RawData, WebSocket } from 'ws';\nimport {\n type RinggSTTOptions,\n type RinggServerEvent,\n type RinggTranscriptEvent,\n buildStartPayload,\n defaultRinggSTTOptions,\n} from './models.js';\n\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\nexport class STT extends stt.STT {\n label = 'ringg.STT';\n #opts: RinggSTTOptions;\n\n constructor(opts: Partial<RinggSTTOptions> = {}) {\n super({ streaming: true, interimResults: true });\n this.#opts = { ...defaultRinggSTTOptions, ...opts };\n if (!this.#opts.apiKey) {\n throw new Error(\n 'Ringg STT: API key is required — set $RINGG_STT_API_KEY or pass { apiKey }.',\n );\n }\n if (!this.#opts.url) {\n throw new Error('Ringg STT: url is required — set $RINGG_STT_URL or pass { url }.');\n }\n }\n\n get provider(): string {\n return 'ringg';\n }\n\n get model(): string {\n return `ringg-parrot-${this.#opts.language}`;\n }\n\n updateOptions(opts: Partial<RinggSTTOptions>): void {\n this.#opts = { ...this.#opts, ...opts };\n }\n\n async _recognize(_frame: AudioBuffer): Promise<stt.SpeechEvent> {\n throw new Error(\n 'Ringg STT plugin is streaming-only; use stream(). ' +\n '(Ringg also exposes a REST /transcriptions endpoint, but it is not wired into this plugin.)',\n );\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): stt.SpeechStream {\n return new SpeechStream(this, this.#opts, options?.connOptions);\n }\n}\n\nexport class SpeechStream extends stt.SpeechStream {\n label = 'ringg.SpeechStream';\n #opts: RinggSTTOptions;\n #logger = log();\n #speaking = false;\n #requestId?: string;\n\n constructor(sttImpl: STT, opts: RinggSTTOptions, connOptions?: APIConnectOptions) {\n // Passing sampleRate as `neededSampleRate` makes the base class resample\n // incoming (typically 48kHz) LiveKit frames down to Ringg's rate for us.\n super(sttImpl, opts.sampleRate, connOptions);\n this.#opts = opts;\n }\n\n protected async run(): Promise<void> {\n const ws = new WebSocket(this.#opts.url);\n ws.binaryType = 'nodebuffer';\n\n // 1) wait for the socket to open\n await new Promise<void>((resolve, reject) => {\n const onErr = (err: Error) => reject(err);\n ws.once('open', () => {\n ws.off('error', onErr);\n resolve();\n });\n ws.once('error', onErr);\n });\n\n // 2) send the `start` handshake and wait for the `ready` reply\n ws.send(JSON.stringify(buildStartPayload(this.#opts)));\n await new Promise<void>((resolve, reject) => {\n const onMsg = (data: RawData, isBinary: boolean) => {\n if (isBinary) return;\n let ev: RinggServerEvent;\n try {\n ev = JSON.parse(data.toString());\n } catch {\n return;\n }\n if (ev.type === 'ready') {\n this.#requestId = (ev as { request_id?: string }).request_id;\n cleanup();\n resolve();\n } else if (ev.type === 'error') {\n cleanup();\n reject(\n new Error(\n `Ringg STT: handshake rejected — ${(ev as { detail?: string }).detail ?? 'unknown error'}`,\n ),\n );\n }\n };\n const onClose = (code: number) => {\n cleanup();\n reject(new Error(`Ringg STT: connection closed during handshake (code ${code})`));\n };\n const cleanup = () => {\n ws.off('message', onMsg);\n ws.off('close', onClose);\n };\n ws.on('message', onMsg);\n ws.once('close', onClose);\n });\n\n this.#logger\n .child({ url: this.#opts.url, language: this.#opts.language, requestId: this.#requestId })\n .debug('ringg stt: connected');\n\n let closed = false;\n const closeWs = () => {\n if (!closed) {\n closed = true;\n try {\n ws.close();\n } catch {\n /* ignore */\n }\n }\n };\n\n // keepalive: application-level JSON ping (Ringg replies with `pong`)\n let keepalive: ReturnType<typeof setInterval> | undefined;\n if (this.#opts.keepaliveIntervalMs > 0) {\n keepalive = setInterval(() => {\n if (ws.readyState === WebSocket.OPEN) {\n try {\n ws.send(JSON.stringify({ type: 'ping' }));\n } catch {\n /* ignore */\n }\n }\n }, this.#opts.keepaliveIntervalMs);\n }\n\n // 3) receive loop — resolves when the server closes the socket\n const recvTask = new Promise<void>((resolve, reject) => {\n ws.on('message', (data: RawData, isBinary: boolean) => {\n if (isBinary) return; // Ringg only sends JSON text frames\n let ev: RinggServerEvent;\n try {\n ev = JSON.parse(data.toString());\n } catch {\n return;\n }\n this.#handleEvent(ev);\n });\n ws.once('close', () => resolve());\n ws.once('error', (err) => reject(err));\n });\n\n // 4) send loop — reads resampled frames from `this.input`, ships raw PCM16\n const sendTask = async () => {\n const samples20ms = Math.floor(this.#opts.sampleRate / 50); // 20ms chunks\n const bstream = new AudioByteStream(this.#opts.sampleRate, 1, samples20ms);\n const shipFrames = (frames: AudioFrame[]) => {\n for (const f of frames) {\n if (ws.readyState === WebSocket.OPEN) {\n // send exactly this frame's bytes (raw PCM16 LE), regardless of view offset\n ws.send(new Uint8Array(f.data.buffer, f.data.byteOffset, f.data.byteLength));\n }\n }\n };\n\n for (;;) {\n if (closed) break;\n const { done, value } = await this.input.next();\n if (done) break;\n if (value === SpeechStream.FLUSH_SENTINEL) {\n shipFrames(bstream.flush());\n } else {\n shipFrames(bstream.write((value as AudioFrame).data.buffer));\n }\n }\n\n // input exhausted: flush remaining audio and ask the server to finalize\n shipFrames(bstream.flush());\n if (ws.readyState === WebSocket.OPEN) {\n ws.send(JSON.stringify({ type: 'end' }));\n }\n };\n\n // abort (session close / cancel) → stop promptly\n const abortPromise = new Promise<void>((resolve) => {\n const signal = this.abortController.signal;\n if (signal.aborted) return resolve();\n signal.addEventListener('abort', () => resolve(), { once: true });\n });\n\n // finite input (a file): after the send loop finishes, wait a grace period\n // for trailing final transcripts, then let the socket close.\n const finiteInputPath = (async () => {\n await sendTask();\n await sleep(this.#opts.finalizeGraceMs);\n })();\n\n try {\n await Promise.race([abortPromise, recvTask, finiteInputPath]);\n } finally {\n if (keepalive) clearInterval(keepalive);\n closeWs();\n // let recvTask settle so its listeners are cleaned up\n await recvTask.catch(() => {});\n }\n }\n\n #handleEvent(ev: RinggServerEvent): void {\n switch (ev.type) {\n case 'transcript': {\n const t = ev as RinggTranscriptEvent;\n const text = (t.transcription ?? '').trim();\n if (!text) return;\n\n if (!this.#speaking) {\n this.#speaking = true;\n this.queue.put({ type: stt.SpeechEventType.START_OF_SPEECH });\n }\n\n const data: stt.SpeechData = {\n language: asLanguageCode(t.language ?? this.#opts.language),\n text,\n startTime: 0,\n endTime: 0,\n confidence: 1.0,\n };\n\n if (t.is_final) {\n this.queue.put({\n type: stt.SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [data],\n requestId: this.#requestId,\n });\n if (this.#speaking) {\n this.#speaking = false;\n this.queue.put({ type: stt.SpeechEventType.END_OF_SPEECH });\n }\n } else {\n this.queue.put({\n type: stt.SpeechEventType.INTERIM_TRANSCRIPT,\n alternatives: [data],\n requestId: this.#requestId,\n });\n }\n break;\n }\n case 'error': {\n this.#logger.child({ event: ev }).error('ringg stt: server error');\n break;\n }\n // ready (duplicate), ack, pong, and unknown types are ignored\n default:\n break;\n }\n }\n}\n","// Types and defaults for the Ringg Parrot STT plugin.\n//\n// The wire protocol is the one implemented by the Python `ringglabs` SDK\n// (ringglabs.stt) and used by the Pipecat `RinggSTTService`:\n// - connect to a WebSocket\n// - send a JSON `start` frame (auth = `api_key` inside this frame)\n// - stream raw binary PCM16 (little-endian, mono) audio frames\n// - receive JSON `transcript` events discriminated by `is_final`\n\nexport type RinggEncoding = 'int16' | 'linear16' | 'float32' | 'int32';\nexport type RinggMode = 'stream' | 'on_final';\n// hi | en are the documented values; keep it open for forward-compat.\nexport type RinggLanguage = 'hi' | 'en' | (string & {});\n\n/** Resolved options for {@link STT}. */\nexport interface RinggSTTOptions {\n /** Full WebSocket URL, e.g. `wss://prod-api.ringg.ai/stt/v1/stream`. */\n url: string;\n /** Ringg API key. Sent inside the `start` JSON frame (not a header). */\n apiKey: string;\n /** Language code sent to the server (default `en`). */\n language: RinggLanguage;\n /** Audio sample rate the plugin sends to Ringg. LiveKit frames are resampled to this. */\n sampleRate: number;\n /** Audio encoding declared to the server. LiveKit gives PCM16, so `int16`. */\n encoding: RinggEncoding;\n /** `stream` = interim + final; `on_final` = finals only. */\n mode: RinggMode;\n /** Ask the server for capitalisation/punctuation. */\n enableCapPunc: boolean;\n /** Server-VAD trailing silence (ms) before finalizing a segment. */\n vadTailSilMs: number;\n /** Server-VAD confidence threshold (0..1). */\n vadConfidence: number;\n /** If true, the client is expected to send speaking-boundary events (advanced). */\n acceptClientVadEvents: boolean;\n /** Application-level keepalive ping interval (ms). 0 disables. */\n keepaliveIntervalMs: number;\n /** After the input ends, how long to keep reading for trailing finals (ms). */\n finalizeGraceMs: number;\n}\n\nconst envInt = (name: string, fallback: number): number => {\n const v = process.env[name];\n if (v === undefined || v === '') return fallback;\n const n = Number(v);\n return Number.isFinite(n) ? n : fallback;\n};\n\nexport const defaultRinggSTTOptions: RinggSTTOptions = {\n url: process.env.RINGG_STT_URL ?? 'wss://prod-api.ringg.ai/stt/v1/stream',\n apiKey: process.env.RINGG_STT_API_KEY ?? '',\n language: (process.env.RINGG_STT_LANGUAGE as RinggLanguage) ?? 'en',\n sampleRate: envInt('RINGG_STT_SAMPLE_RATE', 16000),\n encoding: (process.env.RINGG_STT_ENCODING as RinggEncoding) ?? 'int16',\n mode: (process.env.RINGG_STT_MODE as RinggMode) ?? 'stream',\n enableCapPunc: true,\n vadTailSilMs: 200,\n vadConfidence: 0.55,\n acceptClientVadEvents: false,\n keepaliveIntervalMs: 30000,\n finalizeGraceMs: 1500,\n};\n\n// ---- Server → client wire events (JSON text frames) ----\n\nexport interface RinggReadyEvent {\n type: 'ready';\n request_id: string;\n [k: string]: unknown;\n}\n\nexport interface RinggTranscriptEvent {\n type: 'transcript';\n transcription: string;\n is_final: boolean;\n language?: string;\n request_id?: string;\n segment_idx?: number;\n [k: string]: unknown;\n}\n\nexport interface RinggErrorEvent {\n type: 'error';\n detail: string;\n code?: string;\n status_code?: number;\n}\n\nexport interface RinggAckEvent {\n type: 'ack';\n ack_for: string;\n state: string;\n}\n\nexport interface RinggPongEvent {\n type: 'pong';\n timestamp: number;\n}\n\nexport type RinggServerEvent =\n | RinggReadyEvent\n | RinggTranscriptEvent\n | RinggErrorEvent\n | RinggAckEvent\n | RinggPongEvent\n | { type: string; [k: string]: unknown };\n\n/** Build the JSON `start` handshake payload. */\nexport const buildStartPayload = (opts: RinggSTTOptions): Record<string, unknown> => ({\n type: 'start',\n sample_rate: opts.sampleRate,\n encoding: opts.encoding,\n language: opts.language,\n mode: opts.mode,\n vad_tail_sil_ms: opts.vadTailSilMs,\n vad_confidence: opts.vadConfidence,\n enable_cap_punc: opts.enableCapPunc,\n accept_client_vad_events: opts.acceptClientVadEvents,\n api_key: opts.apiKey,\n});\n"],"mappings":"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","names":["import_agents"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { stt, AudioBuffer, APIConnectOptions } from '@livekit/agents';
|
|
2
|
+
|
|
3
|
+
type RinggEncoding = 'int16' | 'linear16' | 'float32' | 'int32';
|
|
4
|
+
type RinggMode = 'stream' | 'on_final';
|
|
5
|
+
type RinggLanguage = 'hi' | 'en' | (string & {});
|
|
6
|
+
/** Resolved options for {@link STT}. */
|
|
7
|
+
interface RinggSTTOptions {
|
|
8
|
+
/** Full WebSocket URL, e.g. `wss://prod-api.ringg.ai/stt/v1/stream`. */
|
|
9
|
+
url: string;
|
|
10
|
+
/** Ringg API key. Sent inside the `start` JSON frame (not a header). */
|
|
11
|
+
apiKey: string;
|
|
12
|
+
/** Language code sent to the server (default `en`). */
|
|
13
|
+
language: RinggLanguage;
|
|
14
|
+
/** Audio sample rate the plugin sends to Ringg. LiveKit frames are resampled to this. */
|
|
15
|
+
sampleRate: number;
|
|
16
|
+
/** Audio encoding declared to the server. LiveKit gives PCM16, so `int16`. */
|
|
17
|
+
encoding: RinggEncoding;
|
|
18
|
+
/** `stream` = interim + final; `on_final` = finals only. */
|
|
19
|
+
mode: RinggMode;
|
|
20
|
+
/** Ask the server for capitalisation/punctuation. */
|
|
21
|
+
enableCapPunc: boolean;
|
|
22
|
+
/** Server-VAD trailing silence (ms) before finalizing a segment. */
|
|
23
|
+
vadTailSilMs: number;
|
|
24
|
+
/** Server-VAD confidence threshold (0..1). */
|
|
25
|
+
vadConfidence: number;
|
|
26
|
+
/** If true, the client is expected to send speaking-boundary events (advanced). */
|
|
27
|
+
acceptClientVadEvents: boolean;
|
|
28
|
+
/** Application-level keepalive ping interval (ms). 0 disables. */
|
|
29
|
+
keepaliveIntervalMs: number;
|
|
30
|
+
/** After the input ends, how long to keep reading for trailing finals (ms). */
|
|
31
|
+
finalizeGraceMs: number;
|
|
32
|
+
}
|
|
33
|
+
declare const defaultRinggSTTOptions: RinggSTTOptions;
|
|
34
|
+
interface RinggReadyEvent {
|
|
35
|
+
type: 'ready';
|
|
36
|
+
request_id: string;
|
|
37
|
+
[k: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
interface RinggTranscriptEvent {
|
|
40
|
+
type: 'transcript';
|
|
41
|
+
transcription: string;
|
|
42
|
+
is_final: boolean;
|
|
43
|
+
language?: string;
|
|
44
|
+
request_id?: string;
|
|
45
|
+
segment_idx?: number;
|
|
46
|
+
[k: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
interface RinggErrorEvent {
|
|
49
|
+
type: 'error';
|
|
50
|
+
detail: string;
|
|
51
|
+
code?: string;
|
|
52
|
+
status_code?: number;
|
|
53
|
+
}
|
|
54
|
+
interface RinggAckEvent {
|
|
55
|
+
type: 'ack';
|
|
56
|
+
ack_for: string;
|
|
57
|
+
state: string;
|
|
58
|
+
}
|
|
59
|
+
interface RinggPongEvent {
|
|
60
|
+
type: 'pong';
|
|
61
|
+
timestamp: number;
|
|
62
|
+
}
|
|
63
|
+
type RinggServerEvent = RinggReadyEvent | RinggTranscriptEvent | RinggErrorEvent | RinggAckEvent | RinggPongEvent | {
|
|
64
|
+
type: string;
|
|
65
|
+
[k: string]: unknown;
|
|
66
|
+
};
|
|
67
|
+
/** Build the JSON `start` handshake payload. */
|
|
68
|
+
declare const buildStartPayload: (opts: RinggSTTOptions) => Record<string, unknown>;
|
|
69
|
+
|
|
70
|
+
declare class STT extends stt.STT {
|
|
71
|
+
#private;
|
|
72
|
+
label: string;
|
|
73
|
+
constructor(opts?: Partial<RinggSTTOptions>);
|
|
74
|
+
get provider(): string;
|
|
75
|
+
get model(): string;
|
|
76
|
+
updateOptions(opts: Partial<RinggSTTOptions>): void;
|
|
77
|
+
_recognize(_frame: AudioBuffer): Promise<stt.SpeechEvent>;
|
|
78
|
+
stream(options?: {
|
|
79
|
+
connOptions?: APIConnectOptions;
|
|
80
|
+
}): stt.SpeechStream;
|
|
81
|
+
}
|
|
82
|
+
declare class SpeechStream extends stt.SpeechStream {
|
|
83
|
+
#private;
|
|
84
|
+
label: string;
|
|
85
|
+
constructor(sttImpl: STT, opts: RinggSTTOptions, connOptions?: APIConnectOptions);
|
|
86
|
+
protected run(): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { type RinggAckEvent, type RinggEncoding, type RinggErrorEvent, type RinggLanguage, type RinggMode, type RinggPongEvent, type RinggReadyEvent, type RinggSTTOptions, type RinggServerEvent, type RinggTranscriptEvent, STT, SpeechStream, buildStartPayload, defaultRinggSTTOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { stt, AudioBuffer, APIConnectOptions } from '@livekit/agents';
|
|
2
|
+
|
|
3
|
+
type RinggEncoding = 'int16' | 'linear16' | 'float32' | 'int32';
|
|
4
|
+
type RinggMode = 'stream' | 'on_final';
|
|
5
|
+
type RinggLanguage = 'hi' | 'en' | (string & {});
|
|
6
|
+
/** Resolved options for {@link STT}. */
|
|
7
|
+
interface RinggSTTOptions {
|
|
8
|
+
/** Full WebSocket URL, e.g. `wss://prod-api.ringg.ai/stt/v1/stream`. */
|
|
9
|
+
url: string;
|
|
10
|
+
/** Ringg API key. Sent inside the `start` JSON frame (not a header). */
|
|
11
|
+
apiKey: string;
|
|
12
|
+
/** Language code sent to the server (default `en`). */
|
|
13
|
+
language: RinggLanguage;
|
|
14
|
+
/** Audio sample rate the plugin sends to Ringg. LiveKit frames are resampled to this. */
|
|
15
|
+
sampleRate: number;
|
|
16
|
+
/** Audio encoding declared to the server. LiveKit gives PCM16, so `int16`. */
|
|
17
|
+
encoding: RinggEncoding;
|
|
18
|
+
/** `stream` = interim + final; `on_final` = finals only. */
|
|
19
|
+
mode: RinggMode;
|
|
20
|
+
/** Ask the server for capitalisation/punctuation. */
|
|
21
|
+
enableCapPunc: boolean;
|
|
22
|
+
/** Server-VAD trailing silence (ms) before finalizing a segment. */
|
|
23
|
+
vadTailSilMs: number;
|
|
24
|
+
/** Server-VAD confidence threshold (0..1). */
|
|
25
|
+
vadConfidence: number;
|
|
26
|
+
/** If true, the client is expected to send speaking-boundary events (advanced). */
|
|
27
|
+
acceptClientVadEvents: boolean;
|
|
28
|
+
/** Application-level keepalive ping interval (ms). 0 disables. */
|
|
29
|
+
keepaliveIntervalMs: number;
|
|
30
|
+
/** After the input ends, how long to keep reading for trailing finals (ms). */
|
|
31
|
+
finalizeGraceMs: number;
|
|
32
|
+
}
|
|
33
|
+
declare const defaultRinggSTTOptions: RinggSTTOptions;
|
|
34
|
+
interface RinggReadyEvent {
|
|
35
|
+
type: 'ready';
|
|
36
|
+
request_id: string;
|
|
37
|
+
[k: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
interface RinggTranscriptEvent {
|
|
40
|
+
type: 'transcript';
|
|
41
|
+
transcription: string;
|
|
42
|
+
is_final: boolean;
|
|
43
|
+
language?: string;
|
|
44
|
+
request_id?: string;
|
|
45
|
+
segment_idx?: number;
|
|
46
|
+
[k: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
interface RinggErrorEvent {
|
|
49
|
+
type: 'error';
|
|
50
|
+
detail: string;
|
|
51
|
+
code?: string;
|
|
52
|
+
status_code?: number;
|
|
53
|
+
}
|
|
54
|
+
interface RinggAckEvent {
|
|
55
|
+
type: 'ack';
|
|
56
|
+
ack_for: string;
|
|
57
|
+
state: string;
|
|
58
|
+
}
|
|
59
|
+
interface RinggPongEvent {
|
|
60
|
+
type: 'pong';
|
|
61
|
+
timestamp: number;
|
|
62
|
+
}
|
|
63
|
+
type RinggServerEvent = RinggReadyEvent | RinggTranscriptEvent | RinggErrorEvent | RinggAckEvent | RinggPongEvent | {
|
|
64
|
+
type: string;
|
|
65
|
+
[k: string]: unknown;
|
|
66
|
+
};
|
|
67
|
+
/** Build the JSON `start` handshake payload. */
|
|
68
|
+
declare const buildStartPayload: (opts: RinggSTTOptions) => Record<string, unknown>;
|
|
69
|
+
|
|
70
|
+
declare class STT extends stt.STT {
|
|
71
|
+
#private;
|
|
72
|
+
label: string;
|
|
73
|
+
constructor(opts?: Partial<RinggSTTOptions>);
|
|
74
|
+
get provider(): string;
|
|
75
|
+
get model(): string;
|
|
76
|
+
updateOptions(opts: Partial<RinggSTTOptions>): void;
|
|
77
|
+
_recognize(_frame: AudioBuffer): Promise<stt.SpeechEvent>;
|
|
78
|
+
stream(options?: {
|
|
79
|
+
connOptions?: APIConnectOptions;
|
|
80
|
+
}): stt.SpeechStream;
|
|
81
|
+
}
|
|
82
|
+
declare class SpeechStream extends stt.SpeechStream {
|
|
83
|
+
#private;
|
|
84
|
+
label: string;
|
|
85
|
+
constructor(sttImpl: STT, opts: RinggSTTOptions, connOptions?: APIConnectOptions);
|
|
86
|
+
protected run(): Promise<void>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { type RinggAckEvent, type RinggEncoding, type RinggErrorEvent, type RinggLanguage, type RinggMode, type RinggPongEvent, type RinggReadyEvent, type RinggSTTOptions, type RinggServerEvent, type RinggTranscriptEvent, STT, SpeechStream, buildStartPayload, defaultRinggSTTOptions };
|