@livekit/agents-plugin-assemblyai 1.4.11 → 1.5.1
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/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/stt-metadata.test.cjs +122 -0
- package/dist/stt-metadata.test.cjs.map +1 -0
- package/dist/stt-metadata.test.d.cts +2 -0
- package/dist/stt-metadata.test.d.ts +2 -0
- package/dist/stt-metadata.test.d.ts.map +1 -0
- package/dist/stt-metadata.test.js +121 -0
- package/dist/stt-metadata.test.js.map +1 -0
- package/dist/stt.cjs +60 -7
- package/dist/stt.cjs.map +1 -1
- package/dist/stt.d.cts +15 -1
- package/dist/stt.d.ts +15 -1
- package/dist/stt.d.ts.map +1 -1
- package/dist/stt.js +61 -7
- package/dist/stt.js.map +1 -1
- package/dist/stt.test.cjs +41 -0
- package/dist/stt.test.cjs.map +1 -1
- package/dist/stt.test.js +41 -0
- package/dist/stt.test.js.map +1 -1
- package/package.json +7 -7
- package/src/stt-metadata.test.ts +132 -0
- package/src/stt.test.ts +50 -0
- package/src/stt.ts +83 -3
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_agents = require("@livekit/agents");
|
|
3
|
+
var import_rtc_node = require("@livekit/rtc-node");
|
|
4
|
+
var import_node_events = require("node:events");
|
|
5
|
+
var import_vitest = require("vitest");
|
|
6
|
+
var import_ws = require("ws");
|
|
7
|
+
var import_stt = require("./stt.cjs");
|
|
8
|
+
function makeFrame(samplesPerChannel = 800, sampleRate = 16e3) {
|
|
9
|
+
const data = new Int16Array(samplesPerChannel);
|
|
10
|
+
data.fill(1);
|
|
11
|
+
return new import_rtc_node.AudioFrame(data, sampleRate, 1, samplesPerChannel);
|
|
12
|
+
}
|
|
13
|
+
async function startWebSocketServer() {
|
|
14
|
+
const wss = new import_ws.WebSocketServer({ host: "127.0.0.1", port: 0 });
|
|
15
|
+
await (0, import_node_events.once)(wss, "listening");
|
|
16
|
+
const address = wss.address();
|
|
17
|
+
return { wss, baseUrl: `ws://127.0.0.1:${address.port}` };
|
|
18
|
+
}
|
|
19
|
+
async function closeWebSocketServer(wss) {
|
|
20
|
+
await new Promise((resolve) => wss.close(() => resolve()));
|
|
21
|
+
}
|
|
22
|
+
async function waitUntil(predicate, timeoutMs = 1e3) {
|
|
23
|
+
const startedAt = Date.now();
|
|
24
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
25
|
+
if (predicate()) return;
|
|
26
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
27
|
+
}
|
|
28
|
+
throw new Error("timed out waiting for condition");
|
|
29
|
+
}
|
|
30
|
+
async function collectUntilEnd(stream) {
|
|
31
|
+
const events = [];
|
|
32
|
+
for await (const event of stream) {
|
|
33
|
+
events.push(event);
|
|
34
|
+
if (event.type === import_agents.stt.SpeechEventType.END_OF_SPEECH) break;
|
|
35
|
+
}
|
|
36
|
+
return events;
|
|
37
|
+
}
|
|
38
|
+
(0, import_vitest.describe)("AssemblyAI STT metadata", () => {
|
|
39
|
+
(0, import_vitest.it)("maps turn confidence fields onto speech data metadata", async () => {
|
|
40
|
+
const { wss, baseUrl } = await startWebSocketServer();
|
|
41
|
+
let requestUrl = "";
|
|
42
|
+
wss.on("connection", (ws, req) => {
|
|
43
|
+
requestUrl = req.url ?? "";
|
|
44
|
+
ws.on("message", () => {
|
|
45
|
+
ws.send(
|
|
46
|
+
JSON.stringify({
|
|
47
|
+
type: "Turn",
|
|
48
|
+
transcript: "hola mundo",
|
|
49
|
+
utterance: "hola mundo",
|
|
50
|
+
end_of_turn: true,
|
|
51
|
+
language_code: "es",
|
|
52
|
+
language_confidence: 0.94,
|
|
53
|
+
words: [
|
|
54
|
+
{ text: "hola", start: 0, end: 200, confidence: 0.96 },
|
|
55
|
+
{ text: "mundo", start: 200, end: 500, confidence: 0.98 }
|
|
56
|
+
]
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
const assemblyai = new import_stt.STT({
|
|
63
|
+
apiKey: "test-key",
|
|
64
|
+
baseUrl,
|
|
65
|
+
speechModel: "u3-rt-pro"
|
|
66
|
+
});
|
|
67
|
+
const stream = assemblyai.stream({
|
|
68
|
+
connOptions: { maxRetry: 0, retryIntervalMs: 1, timeoutMs: 1e3 }
|
|
69
|
+
});
|
|
70
|
+
await waitUntil(() => requestUrl !== "");
|
|
71
|
+
stream.pushFrame(makeFrame());
|
|
72
|
+
stream.endInput();
|
|
73
|
+
const events = await collectUntilEnd(stream);
|
|
74
|
+
stream.close();
|
|
75
|
+
(0, import_vitest.expect)(new URL(`ws://127.0.0.1${requestUrl}`).pathname).toBe("/v3/ws");
|
|
76
|
+
(0, import_vitest.expect)(
|
|
77
|
+
events.filter((event) => {
|
|
78
|
+
var _a;
|
|
79
|
+
return (_a = event.alternatives) == null ? void 0 : _a[0];
|
|
80
|
+
}).map((event) => {
|
|
81
|
+
var _a, _b, _c, _d;
|
|
82
|
+
return {
|
|
83
|
+
type: event.type,
|
|
84
|
+
language: (_b = (_a = event.alternatives) == null ? void 0 : _a[0]) == null ? void 0 : _b.language,
|
|
85
|
+
metadata: (_d = (_c = event.alternatives) == null ? void 0 : _c[0]) == null ? void 0 : _d.metadata
|
|
86
|
+
};
|
|
87
|
+
})
|
|
88
|
+
).toEqual([
|
|
89
|
+
{
|
|
90
|
+
type: import_agents.stt.SpeechEventType.INTERIM_TRANSCRIPT,
|
|
91
|
+
language: "es",
|
|
92
|
+
metadata: {
|
|
93
|
+
assemblyai: {
|
|
94
|
+
languageConfidence: 0.94
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
type: import_agents.stt.SpeechEventType.PREFLIGHT_TRANSCRIPT,
|
|
100
|
+
language: "es",
|
|
101
|
+
metadata: {
|
|
102
|
+
assemblyai: {
|
|
103
|
+
languageConfidence: 0.94
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: import_agents.stt.SpeechEventType.FINAL_TRANSCRIPT,
|
|
109
|
+
language: "es",
|
|
110
|
+
metadata: {
|
|
111
|
+
assemblyai: {
|
|
112
|
+
languageConfidence: 0.94
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]);
|
|
117
|
+
} finally {
|
|
118
|
+
await closeWebSocketServer(wss);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=stt-metadata.test.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stt-metadata.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { stt as sttLib } from '@livekit/agents';\nimport { AudioFrame } from '@livekit/rtc-node';\nimport { once } from 'node:events';\nimport type { AddressInfo } from 'node:net';\nimport { describe, expect, it } from 'vitest';\nimport { WebSocketServer } from 'ws';\nimport { STT } from './stt.js';\n\nfunction makeFrame(samplesPerChannel = 800, sampleRate = 16000): AudioFrame {\n const data = new Int16Array(samplesPerChannel);\n data.fill(1);\n return new AudioFrame(data, sampleRate, 1, samplesPerChannel);\n}\n\nasync function startWebSocketServer() {\n const wss = new WebSocketServer({ host: '127.0.0.1', port: 0 });\n await once(wss, 'listening');\n const address = wss.address() as AddressInfo;\n return { wss, baseUrl: `ws://127.0.0.1:${address.port}` };\n}\n\nasync function closeWebSocketServer(wss: WebSocketServer): Promise<void> {\n await new Promise<void>((resolve) => wss.close(() => resolve()));\n}\n\nasync function waitUntil(predicate: () => boolean, timeoutMs = 1000): Promise<void> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < timeoutMs) {\n if (predicate()) return;\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n throw new Error('timed out waiting for condition');\n}\n\nasync function collectUntilEnd(stream: sttLib.SpeechStream): Promise<sttLib.SpeechEvent[]> {\n const events: sttLib.SpeechEvent[] = [];\n for await (const event of stream) {\n events.push(event);\n if (event.type === sttLib.SpeechEventType.END_OF_SPEECH) break;\n }\n return events;\n}\n\ndescribe('AssemblyAI STT metadata', () => {\n it('maps turn confidence fields onto speech data metadata', async () => {\n const { wss, baseUrl } = await startWebSocketServer();\n let requestUrl = '';\n\n wss.on('connection', (ws, req) => {\n requestUrl = req.url ?? '';\n ws.on('message', () => {\n ws.send(\n JSON.stringify({\n type: 'Turn',\n transcript: 'hola mundo',\n utterance: 'hola mundo',\n end_of_turn: true,\n language_code: 'es',\n language_confidence: 0.94,\n words: [\n { text: 'hola', start: 0, end: 200, confidence: 0.96 },\n { text: 'mundo', start: 200, end: 500, confidence: 0.98 },\n ],\n }),\n );\n });\n });\n\n try {\n const assemblyai = new STT({\n apiKey: 'test-key',\n baseUrl,\n speechModel: 'u3-rt-pro',\n });\n const stream = assemblyai.stream({\n connOptions: { maxRetry: 0, retryIntervalMs: 1, timeoutMs: 1000 },\n });\n\n await waitUntil(() => requestUrl !== '');\n\n stream.pushFrame(makeFrame());\n stream.endInput();\n\n const events = await collectUntilEnd(stream);\n stream.close();\n\n expect(new URL(`ws://127.0.0.1${requestUrl}`).pathname).toBe('/v3/ws');\n expect(\n events\n .filter((event) => event.alternatives?.[0])\n .map((event) => ({\n type: event.type,\n language: event.alternatives?.[0]?.language,\n metadata: event.alternatives?.[0]?.metadata,\n })),\n ).toEqual([\n {\n type: sttLib.SpeechEventType.INTERIM_TRANSCRIPT,\n language: 'es',\n metadata: {\n assemblyai: {\n languageConfidence: 0.94,\n },\n },\n },\n {\n type: sttLib.SpeechEventType.PREFLIGHT_TRANSCRIPT,\n language: 'es',\n metadata: {\n assemblyai: {\n languageConfidence: 0.94,\n },\n },\n },\n {\n type: sttLib.SpeechEventType.FINAL_TRANSCRIPT,\n language: 'es',\n metadata: {\n assemblyai: {\n languageConfidence: 0.94,\n },\n },\n },\n ]);\n } finally {\n await closeWebSocketServer(wss);\n }\n });\n});\n"],"mappings":"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","names":["sttLib"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stt-metadata.test.d.ts","sourceRoot":"","sources":["../src/stt-metadata.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { stt as sttLib } from "@livekit/agents";
|
|
2
|
+
import { AudioFrame } from "@livekit/rtc-node";
|
|
3
|
+
import { once } from "node:events";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { WebSocketServer } from "ws";
|
|
6
|
+
import { STT } from "./stt.js";
|
|
7
|
+
function makeFrame(samplesPerChannel = 800, sampleRate = 16e3) {
|
|
8
|
+
const data = new Int16Array(samplesPerChannel);
|
|
9
|
+
data.fill(1);
|
|
10
|
+
return new AudioFrame(data, sampleRate, 1, samplesPerChannel);
|
|
11
|
+
}
|
|
12
|
+
async function startWebSocketServer() {
|
|
13
|
+
const wss = new WebSocketServer({ host: "127.0.0.1", port: 0 });
|
|
14
|
+
await once(wss, "listening");
|
|
15
|
+
const address = wss.address();
|
|
16
|
+
return { wss, baseUrl: `ws://127.0.0.1:${address.port}` };
|
|
17
|
+
}
|
|
18
|
+
async function closeWebSocketServer(wss) {
|
|
19
|
+
await new Promise((resolve) => wss.close(() => resolve()));
|
|
20
|
+
}
|
|
21
|
+
async function waitUntil(predicate, timeoutMs = 1e3) {
|
|
22
|
+
const startedAt = Date.now();
|
|
23
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
24
|
+
if (predicate()) return;
|
|
25
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
26
|
+
}
|
|
27
|
+
throw new Error("timed out waiting for condition");
|
|
28
|
+
}
|
|
29
|
+
async function collectUntilEnd(stream) {
|
|
30
|
+
const events = [];
|
|
31
|
+
for await (const event of stream) {
|
|
32
|
+
events.push(event);
|
|
33
|
+
if (event.type === sttLib.SpeechEventType.END_OF_SPEECH) break;
|
|
34
|
+
}
|
|
35
|
+
return events;
|
|
36
|
+
}
|
|
37
|
+
describe("AssemblyAI STT metadata", () => {
|
|
38
|
+
it("maps turn confidence fields onto speech data metadata", async () => {
|
|
39
|
+
const { wss, baseUrl } = await startWebSocketServer();
|
|
40
|
+
let requestUrl = "";
|
|
41
|
+
wss.on("connection", (ws, req) => {
|
|
42
|
+
requestUrl = req.url ?? "";
|
|
43
|
+
ws.on("message", () => {
|
|
44
|
+
ws.send(
|
|
45
|
+
JSON.stringify({
|
|
46
|
+
type: "Turn",
|
|
47
|
+
transcript: "hola mundo",
|
|
48
|
+
utterance: "hola mundo",
|
|
49
|
+
end_of_turn: true,
|
|
50
|
+
language_code: "es",
|
|
51
|
+
language_confidence: 0.94,
|
|
52
|
+
words: [
|
|
53
|
+
{ text: "hola", start: 0, end: 200, confidence: 0.96 },
|
|
54
|
+
{ text: "mundo", start: 200, end: 500, confidence: 0.98 }
|
|
55
|
+
]
|
|
56
|
+
})
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
try {
|
|
61
|
+
const assemblyai = new STT({
|
|
62
|
+
apiKey: "test-key",
|
|
63
|
+
baseUrl,
|
|
64
|
+
speechModel: "u3-rt-pro"
|
|
65
|
+
});
|
|
66
|
+
const stream = assemblyai.stream({
|
|
67
|
+
connOptions: { maxRetry: 0, retryIntervalMs: 1, timeoutMs: 1e3 }
|
|
68
|
+
});
|
|
69
|
+
await waitUntil(() => requestUrl !== "");
|
|
70
|
+
stream.pushFrame(makeFrame());
|
|
71
|
+
stream.endInput();
|
|
72
|
+
const events = await collectUntilEnd(stream);
|
|
73
|
+
stream.close();
|
|
74
|
+
expect(new URL(`ws://127.0.0.1${requestUrl}`).pathname).toBe("/v3/ws");
|
|
75
|
+
expect(
|
|
76
|
+
events.filter((event) => {
|
|
77
|
+
var _a;
|
|
78
|
+
return (_a = event.alternatives) == null ? void 0 : _a[0];
|
|
79
|
+
}).map((event) => {
|
|
80
|
+
var _a, _b, _c, _d;
|
|
81
|
+
return {
|
|
82
|
+
type: event.type,
|
|
83
|
+
language: (_b = (_a = event.alternatives) == null ? void 0 : _a[0]) == null ? void 0 : _b.language,
|
|
84
|
+
metadata: (_d = (_c = event.alternatives) == null ? void 0 : _c[0]) == null ? void 0 : _d.metadata
|
|
85
|
+
};
|
|
86
|
+
})
|
|
87
|
+
).toEqual([
|
|
88
|
+
{
|
|
89
|
+
type: sttLib.SpeechEventType.INTERIM_TRANSCRIPT,
|
|
90
|
+
language: "es",
|
|
91
|
+
metadata: {
|
|
92
|
+
assemblyai: {
|
|
93
|
+
languageConfidence: 0.94
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: sttLib.SpeechEventType.PREFLIGHT_TRANSCRIPT,
|
|
99
|
+
language: "es",
|
|
100
|
+
metadata: {
|
|
101
|
+
assemblyai: {
|
|
102
|
+
languageConfidence: 0.94
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: sttLib.SpeechEventType.FINAL_TRANSCRIPT,
|
|
108
|
+
language: "es",
|
|
109
|
+
metadata: {
|
|
110
|
+
assemblyai: {
|
|
111
|
+
languageConfidence: 0.94
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]);
|
|
116
|
+
} finally {
|
|
117
|
+
await closeWebSocketServer(wss);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=stt-metadata.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stt-metadata.test.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\nimport { stt as sttLib } from '@livekit/agents';\nimport { AudioFrame } from '@livekit/rtc-node';\nimport { once } from 'node:events';\nimport type { AddressInfo } from 'node:net';\nimport { describe, expect, it } from 'vitest';\nimport { WebSocketServer } from 'ws';\nimport { STT } from './stt.js';\n\nfunction makeFrame(samplesPerChannel = 800, sampleRate = 16000): AudioFrame {\n const data = new Int16Array(samplesPerChannel);\n data.fill(1);\n return new AudioFrame(data, sampleRate, 1, samplesPerChannel);\n}\n\nasync function startWebSocketServer() {\n const wss = new WebSocketServer({ host: '127.0.0.1', port: 0 });\n await once(wss, 'listening');\n const address = wss.address() as AddressInfo;\n return { wss, baseUrl: `ws://127.0.0.1:${address.port}` };\n}\n\nasync function closeWebSocketServer(wss: WebSocketServer): Promise<void> {\n await new Promise<void>((resolve) => wss.close(() => resolve()));\n}\n\nasync function waitUntil(predicate: () => boolean, timeoutMs = 1000): Promise<void> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < timeoutMs) {\n if (predicate()) return;\n await new Promise((resolve) => setTimeout(resolve, 10));\n }\n throw new Error('timed out waiting for condition');\n}\n\nasync function collectUntilEnd(stream: sttLib.SpeechStream): Promise<sttLib.SpeechEvent[]> {\n const events: sttLib.SpeechEvent[] = [];\n for await (const event of stream) {\n events.push(event);\n if (event.type === sttLib.SpeechEventType.END_OF_SPEECH) break;\n }\n return events;\n}\n\ndescribe('AssemblyAI STT metadata', () => {\n it('maps turn confidence fields onto speech data metadata', async () => {\n const { wss, baseUrl } = await startWebSocketServer();\n let requestUrl = '';\n\n wss.on('connection', (ws, req) => {\n requestUrl = req.url ?? '';\n ws.on('message', () => {\n ws.send(\n JSON.stringify({\n type: 'Turn',\n transcript: 'hola mundo',\n utterance: 'hola mundo',\n end_of_turn: true,\n language_code: 'es',\n language_confidence: 0.94,\n words: [\n { text: 'hola', start: 0, end: 200, confidence: 0.96 },\n { text: 'mundo', start: 200, end: 500, confidence: 0.98 },\n ],\n }),\n );\n });\n });\n\n try {\n const assemblyai = new STT({\n apiKey: 'test-key',\n baseUrl,\n speechModel: 'u3-rt-pro',\n });\n const stream = assemblyai.stream({\n connOptions: { maxRetry: 0, retryIntervalMs: 1, timeoutMs: 1000 },\n });\n\n await waitUntil(() => requestUrl !== '');\n\n stream.pushFrame(makeFrame());\n stream.endInput();\n\n const events = await collectUntilEnd(stream);\n stream.close();\n\n expect(new URL(`ws://127.0.0.1${requestUrl}`).pathname).toBe('/v3/ws');\n expect(\n events\n .filter((event) => event.alternatives?.[0])\n .map((event) => ({\n type: event.type,\n language: event.alternatives?.[0]?.language,\n metadata: event.alternatives?.[0]?.metadata,\n })),\n ).toEqual([\n {\n type: sttLib.SpeechEventType.INTERIM_TRANSCRIPT,\n language: 'es',\n metadata: {\n assemblyai: {\n languageConfidence: 0.94,\n },\n },\n },\n {\n type: sttLib.SpeechEventType.PREFLIGHT_TRANSCRIPT,\n language: 'es',\n metadata: {\n assemblyai: {\n languageConfidence: 0.94,\n },\n },\n },\n {\n type: sttLib.SpeechEventType.FINAL_TRANSCRIPT,\n language: 'es',\n metadata: {\n assemblyai: {\n languageConfidence: 0.94,\n },\n },\n },\n ]);\n } finally {\n await closeWebSocketServer(wss);\n }\n });\n});\n"],"mappings":"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","names":[]}
|
package/dist/stt.cjs
CHANGED
|
@@ -28,6 +28,14 @@ const U3_PRO_MODELS = ["u3-rt-pro", "u3-rt-pro-beta-1", "universal-3-5-pro"];
|
|
|
28
28
|
function isU3ProModel(model) {
|
|
29
29
|
return U3_PRO_MODELS.includes(model);
|
|
30
30
|
}
|
|
31
|
+
function speechDataMetadata(data) {
|
|
32
|
+
const assemblyai = {};
|
|
33
|
+
if (typeof data.language_confidence === "number") {
|
|
34
|
+
assemblyai.languageConfidence = data.language_confidence;
|
|
35
|
+
}
|
|
36
|
+
if (Object.keys(assemblyai).length === 0) return void 0;
|
|
37
|
+
return { assemblyai };
|
|
38
|
+
}
|
|
31
39
|
const defaultSTTOptions = {
|
|
32
40
|
apiKey: process.env.ASSEMBLYAI_API_KEY,
|
|
33
41
|
sampleRate: 16e3,
|
|
@@ -39,6 +47,9 @@ const defaultSTTOptions = {
|
|
|
39
47
|
class STT extends import_agents.stt.STT {
|
|
40
48
|
#opts;
|
|
41
49
|
#streams = /* @__PURE__ */ new Set();
|
|
50
|
+
// set (user + session))
|
|
51
|
+
#userKeyterms;
|
|
52
|
+
#sessionKeyterms = [];
|
|
42
53
|
label = "assemblyai.STT";
|
|
43
54
|
get model() {
|
|
44
55
|
return this.#opts.speechModel;
|
|
@@ -47,10 +58,19 @@ class STT extends import_agents.stt.STT {
|
|
|
47
58
|
return "AssemblyAI";
|
|
48
59
|
}
|
|
49
60
|
constructor(opts = {}) {
|
|
61
|
+
const rawModel = opts.speechModel ?? defaultSTTOptions.speechModel;
|
|
62
|
+
const supportsCarryover = isU3ProModel(rawModel) || rawModel === "u3-pro";
|
|
63
|
+
if (opts.agentContextCarryover && !supportsCarryover) {
|
|
64
|
+
(0, import_agents.log)().warn(
|
|
65
|
+
`agentContextCarryover is enabled but model '${rawModel}' does not support it; ignoring`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
50
68
|
super({
|
|
51
69
|
streaming: true,
|
|
52
70
|
interimResults: true,
|
|
53
|
-
alignedTranscript: "word"
|
|
71
|
+
alignedTranscript: "word",
|
|
72
|
+
keyterms: true,
|
|
73
|
+
chatContext: (opts.agentContextCarryover ?? false) && supportsCarryover
|
|
54
74
|
});
|
|
55
75
|
if (opts.speechModel === "u3-pro") {
|
|
56
76
|
(0, import_agents.log)().warn("'u3-pro' is deprecated, use 'universal-3-5-pro' instead.");
|
|
@@ -86,22 +106,50 @@ class STT extends import_agents.stt.STT {
|
|
|
86
106
|
apiKey,
|
|
87
107
|
minTurnSilence
|
|
88
108
|
};
|
|
109
|
+
this.#userKeyterms = [...this.#opts.keytermsPrompt ?? []];
|
|
89
110
|
}
|
|
90
111
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
91
112
|
async _recognize(_) {
|
|
92
113
|
throw new Error("Non-streaming recognize is not supported on AssemblyAI STT");
|
|
93
114
|
}
|
|
94
115
|
updateOptions(opts) {
|
|
95
|
-
|
|
116
|
+
const nextOpts = { ...opts };
|
|
117
|
+
if (nextOpts.keytermsPrompt !== void 0) {
|
|
118
|
+
this.#userKeyterms = [...nextOpts.keytermsPrompt];
|
|
119
|
+
nextOpts.keytermsPrompt = [.../* @__PURE__ */ new Set([...this.#userKeyterms, ...this.#sessionKeyterms])];
|
|
120
|
+
}
|
|
121
|
+
this.#opts = { ...this.#opts, ...nextOpts };
|
|
122
|
+
for (const ref of this.#streams) {
|
|
123
|
+
const stream = ref.deref();
|
|
124
|
+
if (stream) {
|
|
125
|
+
stream.updateOptions(nextOpts);
|
|
126
|
+
} else {
|
|
127
|
+
this.#streams.delete(ref);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
_updateSessionKeyterms(keyterms) {
|
|
132
|
+
if (keyterms.length === this.#sessionKeyterms.length && keyterms.every((t, i) => t === this.#sessionKeyterms[i])) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.#sessionKeyterms = [...keyterms];
|
|
136
|
+
const merged = [.../* @__PURE__ */ new Set([...this.#userKeyterms, ...keyterms])];
|
|
137
|
+
this.#opts.keytermsPrompt = merged;
|
|
96
138
|
for (const ref of this.#streams) {
|
|
97
139
|
const stream = ref.deref();
|
|
98
140
|
if (stream) {
|
|
99
|
-
stream.updateOptions(
|
|
141
|
+
stream.updateOptions({ keytermsPrompt: merged });
|
|
100
142
|
} else {
|
|
101
143
|
this.#streams.delete(ref);
|
|
102
144
|
}
|
|
103
145
|
}
|
|
104
146
|
}
|
|
147
|
+
_pushConversationItem(ev) {
|
|
148
|
+
const chatItem = ev.item;
|
|
149
|
+
if (chatItem instanceof import_agents.ChatMessage && chatItem.role === "assistant" && chatItem.textContent) {
|
|
150
|
+
this.updateOptions({ agentContext: chatItem.textContent });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
105
153
|
stream(options) {
|
|
106
154
|
const stream = new SpeechStream(this, this.#opts, options == null ? void 0 : options.connOptions);
|
|
107
155
|
this.#streams.add(new WeakRef(stream));
|
|
@@ -209,8 +257,9 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
209
257
|
end_of_turn_confidence_threshold: this.#opts.endOfTurnConfidenceThreshold,
|
|
210
258
|
min_turn_silence: minSilence,
|
|
211
259
|
max_turn_silence: maxSilence,
|
|
212
|
-
keyterms_prompt: this.#opts.keytermsPrompt !== void 0 ? JSON.stringify(this.#opts.keytermsPrompt) : void 0,
|
|
260
|
+
keyterms_prompt: this.#opts.keytermsPrompt !== void 0 && this.#opts.keytermsPrompt.length > 0 ? JSON.stringify(this.#opts.keytermsPrompt) : void 0,
|
|
213
261
|
language_detection: languageDetection,
|
|
262
|
+
inactivity_timeout: this.#opts.inactivityTimeout,
|
|
214
263
|
prompt: this.#opts.prompt,
|
|
215
264
|
agent_context: this.#opts.agentContext,
|
|
216
265
|
previous_context_n_turns: this.#opts.previousContextNTurns,
|
|
@@ -377,6 +426,7 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
377
426
|
const utterance = data.utterance ?? "";
|
|
378
427
|
const transcript = data.transcript ?? "";
|
|
379
428
|
const language = (0, import_agents.normalizeLanguage)(data.language_code ?? "en");
|
|
429
|
+
const metadata = speechDataMetadata(data);
|
|
380
430
|
const timedWords = words.map(
|
|
381
431
|
(word) => (0, import_agents.createTimedString)({
|
|
382
432
|
text: word.text ?? "",
|
|
@@ -403,7 +453,8 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
403
453
|
startTime,
|
|
404
454
|
endTime,
|
|
405
455
|
confidence,
|
|
406
|
-
words: timedWords
|
|
456
|
+
words: timedWords,
|
|
457
|
+
...metadata ? { metadata } : {}
|
|
407
458
|
}
|
|
408
459
|
]
|
|
409
460
|
});
|
|
@@ -425,7 +476,8 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
425
476
|
startTime: this.#lastPreflightStartTime,
|
|
426
477
|
endTime,
|
|
427
478
|
confidence: utteranceConfidence,
|
|
428
|
-
words: utteranceWords
|
|
479
|
+
words: utteranceWords,
|
|
480
|
+
...metadata ? { metadata } : {}
|
|
429
481
|
}
|
|
430
482
|
]
|
|
431
483
|
});
|
|
@@ -442,7 +494,8 @@ class SpeechStream extends import_agents.stt.SpeechStream {
|
|
|
442
494
|
startTime,
|
|
443
495
|
endTime,
|
|
444
496
|
confidence,
|
|
445
|
-
words: timedWords
|
|
497
|
+
words: timedWords,
|
|
498
|
+
...metadata ? { metadata } : {}
|
|
446
499
|
}
|
|
447
500
|
]
|
|
448
501
|
});
|
package/dist/stt.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/stt.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n//\nimport {\n type APIConnectOptions,\n type AudioBuffer,\n AudioByteStream,\n Future,\n Task,\n createTimedString,\n delay,\n log,\n normalizeLanguage,\n stt,\n waitForAbort,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { RawData } from 'ws';\nimport { WebSocket } from 'ws';\nimport type { STTEncoding, STTModels, VoiceFocus } from './models.js';\n\n// Speech models in the Universal-3 Pro family, which share the same parameter support.\nconst U3_PRO_MODELS = ['u3-rt-pro', 'u3-rt-pro-beta-1', 'universal-3-5-pro'] as const;\n\nfunction isU3ProModel(model: STTModels): boolean {\n return U3_PRO_MODELS.includes(model as (typeof U3_PRO_MODELS)[number]);\n}\n\n// AssemblyAI Universal-Streaming (v3) message envelope. All fields are optional\n// since we narrow on `type` before reading anything else.\ninterface StreamEventMessage {\n type?: 'Begin' | 'SpeechStarted' | 'Turn' | 'Termination' | string;\n // Begin\n id?: string;\n expires_at?: number;\n // Turn\n transcript?: string;\n utterance?: string;\n end_of_turn?: boolean;\n end_of_turn_confidence?: number;\n turn_is_formatted?: boolean;\n language_code?: string;\n speaker_label?: string;\n words?: Array<{\n text?: string;\n start?: number;\n end?: number;\n confidence?: number;\n speaker?: string;\n }>;\n // Termination\n audio_duration_seconds?: number;\n session_duration_seconds?: number;\n}\n\nexport interface STTOptions {\n apiKey?: string;\n sampleRate: number;\n /**\n * How large each chunk of audio is before being sent to AssemblyAI, in\n * milliseconds. Corresponds to Python's `buffer_size_seconds` (seconds there,\n * ms here per this repo's time-unit convention).\n */\n bufferSizeMs: number;\n encoding: STTEncoding;\n speechModel: STTModels;\n languageDetection?: boolean;\n endOfTurnConfidenceThreshold?: number;\n /** Minimum silence (ms) before a confident end-of-turn is finalized. */\n minTurnSilence?: number;\n /** Maximum silence (ms) before end-of-turn is forced regardless of confidence. */\n maxTurnSilence?: number;\n formatTurns?: boolean;\n keytermsPrompt?: string[];\n /** Only supported with the Universal-3 Pro model family. */\n prompt?: string;\n /** Only supported with the Universal-3 Pro model family. */\n agentContext?: string;\n /** Only supported with the Universal-3 Pro model family. Set at connection time only. */\n previousContextNTurns?: number;\n vadThreshold?: number;\n /**\n * Enable speaker diarization. Note: AssemblyAI will return per-word speaker\n * labels, but the JS framework's `stt.SpeechData` type does not yet expose\n * a `speakerId` field (unlike the Python framework), so the labels are not\n * currently surfaced on emitted events. Setting this to `true` still has\n * effect server-side. Once the base `SpeechData` interface gains speaker\n * support, `#processStreamEvent` should forward `data.words[].speaker` too.\n */\n speakerLabels?: boolean;\n maxSpeakers?: number;\n domain?: string;\n /** Isolate the primary voice and suppress background noise. Connect-time only. */\n voiceFocus?: VoiceFocus;\n /** Background audio suppression aggressiveness, from 0.0 to 1.0. Connect-time only. */\n voiceFocusThreshold?: number;\n /**\n * Accuracy/latency preset for the Universal-3 Pro model family: `min_latency`, `balanced`,\n * or `max_accuracy`. Explicit turn-silence values still take precedence over mode defaults.\n */\n mode?: 'min_latency' | 'balanced' | 'max_accuracy';\n baseUrl: string;\n}\n\nconst defaultSTTOptions: STTOptions = {\n apiKey: process.env.ASSEMBLYAI_API_KEY,\n sampleRate: 16000,\n bufferSizeMs: 50,\n encoding: 'pcm_s16le',\n speechModel: 'universal-3-5-pro',\n baseUrl: 'wss://streaming.assemblyai.com',\n};\n\nexport class STT extends stt.STT {\n #opts: STTOptions;\n #streams = new Set<WeakRef<SpeechStream>>();\n label = 'assemblyai.STT';\n\n get model(): string {\n return this.#opts.speechModel;\n }\n\n get provider(): string {\n return 'AssemblyAI';\n }\n\n constructor(opts: Partial<STTOptions> = {}) {\n super({\n streaming: true,\n interimResults: true,\n alignedTranscript: 'word',\n });\n\n if (opts.speechModel === 'u3-pro') {\n log().warn(\"'u3-pro' is deprecated, use 'universal-3-5-pro' instead.\");\n opts.speechModel = 'universal-3-5-pro';\n }\n\n const speechModel = opts.speechModel ?? defaultSTTOptions.speechModel;\n if (!isU3ProModel(speechModel)) {\n for (const param of [\n 'prompt',\n 'agentContext',\n 'previousContextNTurns',\n 'voiceFocus',\n 'voiceFocusThreshold',\n 'mode',\n ] as const) {\n if (opts[param] !== undefined) {\n throw new Error(\n `The '${param}' parameter is only supported with the ${U3_PRO_MODELS.join(', ')} models.`,\n );\n }\n }\n }\n\n const apiKey = opts.apiKey ?? defaultSTTOptions.apiKey;\n if (!apiKey) {\n throw new Error(\n 'AssemblyAI API key is required. Pass one in via the `apiKey` parameter, or set it as the `ASSEMBLYAI_API_KEY` environment variable',\n );\n }\n\n // Minimize latency by default, but let AssemblyAI's mode preset control silence tuning.\n const minTurnSilence = opts.minTurnSilence ?? (opts.mode === undefined ? 100 : undefined);\n\n this.#opts = {\n ...defaultSTTOptions,\n ...opts,\n apiKey,\n minTurnSilence,\n };\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async _recognize(_: AudioBuffer): Promise<stt.SpeechEvent> {\n throw new Error('Non-streaming recognize is not supported on AssemblyAI STT');\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n for (const ref of this.#streams) {\n const stream = ref.deref();\n if (stream) {\n stream.updateOptions(opts);\n } else {\n this.#streams.delete(ref);\n }\n }\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): SpeechStream {\n const stream = new SpeechStream(this, this.#opts, options?.connOptions);\n this.#streams.add(new WeakRef(stream));\n return stream;\n }\n}\n\nexport class SpeechStream extends stt.SpeechStream {\n static readonly CLOSE_MSG = JSON.stringify({ type: 'Terminate' });\n\n #opts: STTOptions;\n #logger = log();\n #speechDurationInS = 0;\n #lastPreflightStartTime = 0;\n #pendingConfigMessages: Record<string, unknown>[] = [];\n #configMessagePending = new Future();\n #sessionId: string | null = null;\n #expiresAt: number | null = null;\n label = 'assemblyai.SpeechStream';\n\n constructor(stt: STT, opts: STTOptions, connOptions?: APIConnectOptions) {\n super(stt, opts.sampleRate, connOptions);\n this.#opts = opts;\n this.closed = false;\n }\n\n /**\n * The AssemblyAI session ID. Set when the WebSocket connection is established\n * (before any speech events). Null until the connection completes.\n * Share this with the AssemblyAI team when reporting issues.\n */\n get sessionId(): string | null {\n return this.#sessionId;\n }\n\n /**\n * Unix timestamp when the AssemblyAI session expires. Set alongside\n * {@link sessionId} when the WebSocket connection is established.\n */\n get expiresAt(): number | null {\n return this.#expiresAt;\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n\n const configMsg: Record<string, unknown> = { type: 'UpdateConfiguration' };\n if (opts.prompt !== undefined) configMsg.prompt = opts.prompt;\n if (opts.agentContext !== undefined) configMsg.agent_context = opts.agentContext;\n if (opts.keytermsPrompt !== undefined) configMsg.keyterms_prompt = opts.keytermsPrompt;\n if (opts.maxTurnSilence !== undefined) configMsg.max_turn_silence = opts.maxTurnSilence;\n if (opts.minTurnSilence !== undefined) configMsg.min_turn_silence = opts.minTurnSilence;\n if (opts.endOfTurnConfidenceThreshold !== undefined) {\n configMsg.end_of_turn_confidence_threshold = opts.endOfTurnConfidenceThreshold;\n }\n if (opts.vadThreshold !== undefined) configMsg.vad_threshold = opts.vadThreshold;\n\n // Only send if any actual fields (besides `type`) were specified.\n if (Object.keys(configMsg).length > 1) {\n this.#pendingConfigMessages.push(configMsg);\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n }\n\n /**\n * Force-finalize the current turn immediately.\n */\n forceEndpoint() {\n this.#pendingConfigMessages.push({ type: 'ForceEndpoint' });\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n\n // Deepgram-style reconnect loop around a single websocket lifetime.\n protected async run() {\n const maxRetry = 32;\n let retries = 0;\n\n while (!this.input.closed && !this.closed) {\n try {\n const ws = await this.#connectWS();\n await this.#runWS(ws);\n retries = 0;\n } catch (e) {\n if (!this.closed && !this.input.closed) {\n if (retries >= maxRetry) {\n throw new Error(`failed to connect to AssemblyAI after ${retries} attempts: ${e}`);\n }\n\n const retryDelaySeconds = Math.min(retries * 5, 10);\n retries++;\n\n this.#logger.warn(\n `failed to connect to AssemblyAI, retrying in ${retryDelaySeconds} seconds: ${e} (${retries}/${maxRetry})`,\n );\n await delay(retryDelaySeconds * 1000);\n } else {\n this.#logger.warn(\n `AssemblyAI disconnected, connection is closed: ${e} (inputClosed: ${this.input.closed}, isClosed: ${this.closed})`,\n );\n }\n }\n }\n\n this.closed = true;\n }\n\n async #connectWS(): Promise<WebSocket> {\n // Universal-3 Pro family models default both min and max silence to 100ms when unset.\n // When a mode preset is selected, leave them unset unless explicitly provided so the\n // server's per-mode silence tuning is not overridden by the latency-optimized default.\n let minSilence = this.#opts.minTurnSilence;\n let maxSilence = this.#opts.maxTurnSilence;\n if (isU3ProModel(this.#opts.speechModel)) {\n if (minSilence === undefined && this.#opts.mode === undefined) minSilence = 100;\n if (maxSilence === undefined) maxSilence = minSilence;\n }\n\n // Default language_detection to true for multilingual / u3-rt-pro-family models, false otherwise.\n const defaultLanguageDetection =\n this.#opts.speechModel.includes('multilingual') || isU3ProModel(this.#opts.speechModel);\n const languageDetection = this.#opts.languageDetection ?? defaultLanguageDetection;\n\n const liveConfig: Record<string, unknown> = {\n sample_rate: this.#opts.sampleRate,\n encoding: this.#opts.encoding,\n speech_model: this.#opts.speechModel,\n format_turns: this.#opts.formatTurns,\n end_of_turn_confidence_threshold: this.#opts.endOfTurnConfidenceThreshold,\n min_turn_silence: minSilence,\n max_turn_silence: maxSilence,\n keyterms_prompt:\n this.#opts.keytermsPrompt !== undefined\n ? JSON.stringify(this.#opts.keytermsPrompt)\n : undefined,\n language_detection: languageDetection,\n prompt: this.#opts.prompt,\n agent_context: this.#opts.agentContext,\n previous_context_n_turns: this.#opts.previousContextNTurns,\n vad_threshold: this.#opts.vadThreshold,\n speaker_labels: this.#opts.speakerLabels,\n max_speakers: this.#opts.maxSpeakers,\n domain: this.#opts.domain,\n voice_focus: this.#opts.voiceFocus,\n voice_focus_threshold: this.#opts.voiceFocusThreshold,\n mode: this.#opts.mode,\n };\n\n const url = new URL(`${this.#opts.baseUrl}/v3/ws`);\n // Python serializes booleans as the strings \"true\"/\"false\", so we mirror that.\n for (const [key, value] of Object.entries(liveConfig)) {\n if (value === undefined || value === null) continue;\n if (typeof value === 'boolean') {\n url.searchParams.append(key, value ? 'true' : 'false');\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n\n const ws = new WebSocket(url, {\n headers: {\n Authorization: this.#opts.apiKey!,\n 'Content-Type': 'application/json',\n 'User-Agent': 'AssemblyAI/1.0 (integration=Livekit)',\n },\n });\n\n await new Promise<void>((resolve, reject) => {\n ws.on('open', () => resolve());\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(new Error(`WebSocket returned ${code}`)));\n });\n\n return ws;\n }\n\n async #runWS(ws: WebSocket) {\n let closing = false;\n const sessionController = new AbortController();\n\n // gets cancelled also when sendTask is complete\n const wsMonitor = Task.from(async (controller) => {\n const closed = new Promise<void>((_, reject) => {\n ws.once('close', (code, reason) => {\n if (!closing) {\n this.#logger.error(`WebSocket closed with code ${code}: ${reason}`);\n reject(new Error('WebSocket closed'));\n }\n });\n });\n\n await Promise.race([closed, waitForAbort(controller.signal)]);\n });\n\n const sendTask = async () => {\n const samplesPerBuffer = Math.floor((this.#opts.sampleRate * this.#opts.bufferSizeMs) / 1000);\n const audioStream = new AudioByteStream(this.#opts.sampleRate, 1, samplesPerBuffer);\n\n const abortPromise = waitForAbort(this.abortSignal);\n const sessionAbort = waitForAbort(sessionController.signal);\n\n try {\n while (!this.closed) {\n const result = await Promise.race([this.input.next(), abortPromise, sessionAbort]);\n\n if (result === undefined) return; // aborted\n if (result.done) break;\n\n const data = result.value;\n\n let frames: AudioFrame[];\n if (data === SpeechStream.FLUSH_SENTINEL) {\n frames = audioStream.flush();\n } else if (data.sampleRate === this.#opts.sampleRate && data.channels === 1) {\n // AssemblyAI expects mono PCM. The base SpeechStream only resamples\n // sample rate, so reject any frame that is not already downmixed.\n frames = audioStream.write(data.data.buffer as ArrayBuffer);\n } else {\n throw new Error('sample rate or channel count of frame does not match');\n }\n\n for (const frame of frames) {\n this.#speechDurationInS += frame.samplesPerChannel / frame.sampleRate;\n ws.send(frame.data.buffer);\n }\n }\n } finally {\n closing = true;\n try {\n ws.send(SpeechStream.CLOSE_MSG);\n } catch {\n // ignore — socket may already be closing\n }\n wsMonitor.cancel();\n }\n };\n\n let messageHandler: ((msg: RawData, isBinary: boolean) => void) | null = null;\n const listenTask = Task.from(async (controller) => {\n const listenMessage = new Promise<void>((resolve, reject) => {\n messageHandler = (msg, isBinary) => {\n if (isBinary) {\n this.#logger.error('unexpected binary message from AssemblyAI');\n return;\n }\n try {\n const json = JSON.parse(msg.toString()) as StreamEventMessage;\n this.#processStreamEvent(json);\n if (this.closed || closing) {\n resolve();\n }\n } catch (err) {\n this.#logger.error(`AssemblyAI: error processing message: ${msg}`);\n reject(err);\n }\n };\n ws.on('message', messageHandler);\n });\n\n await Promise.race([listenMessage, waitForAbort(controller.signal)]);\n });\n\n const configTask = Task.from(async (controller) => {\n // Drain any messages queued while the socket was reconnecting.\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n\n while (!controller.signal.aborted) {\n await Promise.race([this.#configMessagePending.await, waitForAbort(controller.signal)]);\n if (controller.signal.aborted) return;\n\n this.#configMessagePending = new Future();\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n }\n });\n\n try {\n await Promise.all([sendTask(), listenTask.result, wsMonitor.result]);\n } finally {\n closing = true;\n sessionController.abort();\n listenTask.cancel();\n configTask.cancel();\n if (messageHandler) ws.off('message', messageHandler);\n try {\n ws.close();\n } catch {\n // ignore\n }\n }\n }\n\n #averageConfidence(words: Array<{ confidence?: number }>): number {\n if (words.length === 0) return 0;\n return words.reduce((sum, w) => sum + (w.confidence ?? 0), 0) / words.length;\n }\n\n #processStreamEvent(data: StreamEventMessage) {\n const messageType = data.type;\n\n if (messageType === 'Begin') {\n this.#sessionId = data.id ?? null;\n this.#expiresAt = data.expires_at ?? null;\n this.#logger.info(\n `AssemblyAI session started id=${this.#sessionId} expires_at=${this.#expiresAt}`,\n );\n return;\n }\n\n if (messageType === 'SpeechStarted') {\n this.queue.put({ type: stt.SpeechEventType.START_OF_SPEECH });\n return;\n }\n\n if (messageType === 'Termination') {\n this.#logger.debug(\n `AssemblyAI session terminated audio_duration=${data.audio_duration_seconds}s session_duration=${data.session_duration_seconds}s`,\n );\n return;\n }\n\n if (messageType !== 'Turn') {\n return;\n }\n\n const words = data.words ?? [];\n const endOfTurn = Boolean(data.end_of_turn);\n const turnIsFormatted = Boolean(data.turn_is_formatted);\n const utterance = data.utterance ?? '';\n const transcript = data.transcript ?? '';\n const language = normalizeLanguage(data.language_code ?? 'en');\n\n // Word timestamps are in milliseconds:\n // https://www.assemblyai.com/docs/api-reference/streaming-api/streaming-api#receive.receiveTurn.words\n const timedWords = words.map((word) =>\n createTimedString({\n text: word.text ?? '',\n startTime: (word.start ?? 0) / 1000 + this.startTimeOffset,\n endTime: (word.end ?? 0) / 1000 + this.startTimeOffset,\n confidence: word.confidence ?? 0,\n startTimeOffset: this.startTimeOffset,\n }),\n );\n\n let startTime = 0;\n let endTime = 0;\n let confidence = 0;\n\n // `words` are cumulative for the turn — emit as an interim transcript.\n if (timedWords.length > 0) {\n const interimText = timedWords.map((w) => w.text).join(' ');\n startTime = timedWords[0]!.startTime ?? 0;\n endTime = timedWords[timedWords.length - 1]!.endTime ?? 0;\n confidence = this.#averageConfidence(timedWords);\n\n this.queue.put({\n type: stt.SpeechEventType.INTERIM_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: interimText,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n },\n ],\n });\n }\n\n // `utterance` is chunk-based (not cumulative) — emit as a preflight transcript\n // covering only the words since the last preflight.\n if (utterance) {\n if (this.#lastPreflightStartTime === 0) {\n this.#lastPreflightStartTime = startTime;\n }\n\n const utteranceWords = timedWords.filter(\n (w) => w.startTime !== undefined && w.startTime >= this.#lastPreflightStartTime,\n );\n const utteranceConfidence = this.#averageConfidence(utteranceWords);\n\n this.queue.put({\n type: stt.SpeechEventType.PREFLIGHT_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: utterance,\n startTime: this.#lastPreflightStartTime,\n endTime,\n confidence: utteranceConfidence,\n words: utteranceWords,\n },\n ],\n });\n this.#lastPreflightStartTime = endTime;\n }\n\n // End-of-turn: emit FINAL_TRANSCRIPT + END_OF_SPEECH.\n // If the user asked for formatted turns, wait for a formatted final.\n const waitingForFormatted = this.#opts.formatTurns === true && !turnIsFormatted;\n if (endOfTurn && !waitingForFormatted) {\n this.queue.put({\n type: stt.SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: transcript,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n },\n ],\n });\n\n this.queue.put({ type: stt.SpeechEventType.END_OF_SPEECH });\n if (this.#speechDurationInS > 0) {\n this.queue.put({\n type: stt.SpeechEventType.RECOGNITION_USAGE,\n // Propagate the AssemblyAI session id as the request id so metrics\n // can be correlated back to a specific connection, mirroring how\n // Deepgram surfaces its `request_id`.\n requestId: this.#sessionId ?? undefined,\n recognitionUsage: {\n audioDuration: this.#speechDurationInS,\n },\n });\n this.#speechDurationInS = 0;\n this.#lastPreflightStartTime = 0;\n }\n }\n }\n}\n"],"mappings":"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","names":["stt"]}
|
|
1
|
+
{"version":3,"sources":["../src/stt.ts"],"sourcesContent":["// SPDX-FileCopyrightText: 2026 LiveKit, Inc.\n//\n// SPDX-License-Identifier: Apache-2.0\n//\nimport {\n type APIConnectOptions,\n type AudioBuffer,\n AudioByteStream,\n ChatMessage,\n type ConversationItemAddedEvent,\n Future,\n Task,\n createTimedString,\n delay,\n log,\n normalizeLanguage,\n stt,\n waitForAbort,\n} from '@livekit/agents';\nimport type { AudioFrame } from '@livekit/rtc-node';\nimport type { RawData } from 'ws';\nimport { WebSocket } from 'ws';\nimport type { STTEncoding, STTModels, VoiceFocus } from './models.js';\n\n// Speech models in the Universal-3 Pro family, which share the same parameter support.\nconst U3_PRO_MODELS = ['u3-rt-pro', 'u3-rt-pro-beta-1', 'universal-3-5-pro'] as const;\n\nfunction isU3ProModel(model: STTModels): boolean {\n return U3_PRO_MODELS.includes(model as (typeof U3_PRO_MODELS)[number]);\n}\n\n// AssemblyAI Universal-Streaming (v3) message envelope. All fields are optional\n// since we narrow on `type` before reading anything else.\ninterface StreamEventMessage {\n type?: 'Begin' | 'SpeechStarted' | 'Turn' | 'Termination' | string;\n // Begin\n id?: string;\n expires_at?: number;\n // Turn\n transcript?: string;\n utterance?: string;\n end_of_turn?: boolean;\n end_of_turn_confidence?: number;\n turn_is_formatted?: boolean;\n language_code?: string;\n language_confidence?: number;\n speaker_label?: string;\n words?: Array<{\n text?: string;\n start?: number;\n end?: number;\n confidence?: number;\n speaker?: string;\n }>;\n // Termination\n audio_duration_seconds?: number;\n session_duration_seconds?: number;\n}\n\nfunction speechDataMetadata(data: StreamEventMessage): stt.SpeechData['metadata'] | undefined {\n const assemblyai: Record<string, number> = {};\n\n if (typeof data.language_confidence === 'number') {\n assemblyai.languageConfidence = data.language_confidence;\n }\n\n if (Object.keys(assemblyai).length === 0) return undefined;\n\n return { assemblyai };\n}\n\nexport interface STTOptions {\n apiKey?: string;\n sampleRate: number;\n /**\n * How large each chunk of audio is before being sent to AssemblyAI, in\n * milliseconds. Corresponds to Python's `buffer_size_seconds` (seconds there,\n * ms here per this repo's time-unit convention).\n */\n bufferSizeMs: number;\n encoding: STTEncoding;\n speechModel: STTModels;\n languageDetection?: boolean;\n /**\n * Session inactivity timeout in seconds. AssemblyAI accepts integer values\n * from 5 to 3600; when unset, no inactivity timeout is applied.\n */\n inactivityTimeout?: number;\n endOfTurnConfidenceThreshold?: number;\n /** Minimum silence (ms) before a confident end-of-turn is finalized. */\n minTurnSilence?: number;\n /** Maximum silence (ms) before end-of-turn is forced regardless of confidence. */\n maxTurnSilence?: number;\n formatTurns?: boolean;\n keytermsPrompt?: string[];\n /** Only supported with the Universal-3 Pro model family. */\n prompt?: string;\n /** Only supported with the Universal-3 Pro model family. */\n agentContext?: string;\n /** Only supported with the Universal-3 Pro model family. Set at connection time only. */\n previousContextNTurns?: number;\n vadThreshold?: number;\n /**\n * Enable speaker diarization. Note: AssemblyAI will return per-word speaker\n * labels, but the JS framework's `stt.SpeechData` type does not yet expose\n * a `speakerId` field (unlike the Python framework), so the labels are not\n * currently surfaced on emitted events. Setting this to `true` still has\n * effect server-side. Once the base `SpeechData` interface gains speaker\n * support, `#processStreamEvent` should forward `data.words[].speaker` too.\n */\n speakerLabels?: boolean;\n maxSpeakers?: number;\n domain?: string;\n /** Isolate the primary voice and suppress background noise. Connect-time only. */\n voiceFocus?: VoiceFocus;\n /** Background audio suppression aggressiveness, from 0.0 to 1.0. Connect-time only. */\n voiceFocusThreshold?: number;\n /**\n * Accuracy/latency preset for the Universal-3 Pro model family: `min_latency`, `balanced`,\n * or `max_accuracy`. Explicit turn-silence values still take precedence over mode defaults.\n */\n mode?: 'min_latency' | 'balanced' | 'max_accuracy';\n /**\n * When the model supports it, let an `AgentSession` push each assistant reply into\n * `agentContext` so it is carried into the model's conversation context. Defaults to false;\n * set true to enable. Prior user turns are carried automatically by the model regardless of\n * this flag. Ignored on models without context support.\n */\n agentContextCarryover?: boolean;\n baseUrl: string;\n}\n\nconst defaultSTTOptions: STTOptions = {\n apiKey: process.env.ASSEMBLYAI_API_KEY,\n sampleRate: 16000,\n bufferSizeMs: 50,\n encoding: 'pcm_s16le',\n speechModel: 'universal-3-5-pro',\n baseUrl: 'wss://streaming.assemblyai.com',\n};\n\nexport class STT extends stt.STT {\n #opts: STTOptions;\n #streams = new Set<WeakRef<SpeechStream>>();\n // set (user + session))\n #userKeyterms: string[];\n #sessionKeyterms: string[] = [];\n label = 'assemblyai.STT';\n\n get model(): string {\n return this.#opts.speechModel;\n }\n\n get provider(): string {\n return 'AssemblyAI';\n }\n\n constructor(opts: Partial<STTOptions> = {}) {\n // u3-rt-pro family — \"u3-pro\" is normalized below — and is opt-in via the user)\n const rawModel = opts.speechModel ?? defaultSTTOptions.speechModel;\n const supportsCarryover = isU3ProModel(rawModel) || rawModel === 'u3-pro';\n if (opts.agentContextCarryover && !supportsCarryover) {\n log().warn(\n `agentContextCarryover is enabled but model '${rawModel}' does not support it; ignoring`,\n );\n }\n super({\n streaming: true,\n interimResults: true,\n alignedTranscript: 'word',\n keyterms: true,\n chatContext: (opts.agentContextCarryover ?? false) && supportsCarryover,\n });\n\n if (opts.speechModel === 'u3-pro') {\n log().warn(\"'u3-pro' is deprecated, use 'universal-3-5-pro' instead.\");\n opts.speechModel = 'universal-3-5-pro';\n }\n\n const speechModel = opts.speechModel ?? defaultSTTOptions.speechModel;\n if (!isU3ProModel(speechModel)) {\n for (const param of [\n 'prompt',\n 'agentContext',\n 'previousContextNTurns',\n 'voiceFocus',\n 'voiceFocusThreshold',\n 'mode',\n ] as const) {\n if (opts[param] !== undefined) {\n throw new Error(\n `The '${param}' parameter is only supported with the ${U3_PRO_MODELS.join(', ')} models.`,\n );\n }\n }\n }\n\n const apiKey = opts.apiKey ?? defaultSTTOptions.apiKey;\n if (!apiKey) {\n throw new Error(\n 'AssemblyAI API key is required. Pass one in via the `apiKey` parameter, or set it as the `ASSEMBLYAI_API_KEY` environment variable',\n );\n }\n\n // Minimize latency by default, but let AssemblyAI's mode preset control silence tuning.\n const minTurnSilence = opts.minTurnSilence ?? (opts.mode === undefined ? 100 : undefined);\n\n this.#opts = {\n ...defaultSTTOptions,\n ...opts,\n apiKey,\n minTurnSilence,\n };\n this.#userKeyterms = [...(this.#opts.keytermsPrompt ?? [])];\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n async _recognize(_: AudioBuffer): Promise<stt.SpeechEvent> {\n throw new Error('Non-streaming recognize is not supported on AssemblyAI STT');\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n // session keyterms so a user update doesn't drop them)\n const nextOpts = { ...opts };\n if (nextOpts.keytermsPrompt !== undefined) {\n this.#userKeyterms = [...nextOpts.keytermsPrompt];\n nextOpts.keytermsPrompt = [...new Set([...this.#userKeyterms, ...this.#sessionKeyterms])];\n }\n this.#opts = { ...this.#opts, ...nextOpts };\n for (const ref of this.#streams) {\n const stream = ref.deref();\n if (stream) {\n stream.updateOptions(nextOpts);\n } else {\n this.#streams.delete(ref);\n }\n }\n }\n\n override _updateSessionKeyterms(keyterms: string[]): void {\n if (\n keyterms.length === this.#sessionKeyterms.length &&\n keyterms.every((t, i) => t === this.#sessionKeyterms[i])\n ) {\n return;\n }\n this.#sessionKeyterms = [...keyterms];\n const merged = [...new Set([...this.#userKeyterms, ...keyterms])];\n this.#opts.keytermsPrompt = merged;\n // applied live via the stream's UpdateConfiguration (no reconnect)\n for (const ref of this.#streams) {\n const stream = ref.deref();\n if (stream) {\n stream.updateOptions({ keytermsPrompt: merged });\n } else {\n this.#streams.delete(ref);\n }\n }\n }\n\n override _pushConversationItem(ev: ConversationItemAddedEvent): void {\n const chatItem = ev.item;\n if (chatItem instanceof ChatMessage && chatItem.role === 'assistant' && chatItem.textContent) {\n this.updateOptions({ agentContext: chatItem.textContent });\n }\n }\n\n stream(options?: { connOptions?: APIConnectOptions }): SpeechStream {\n const stream = new SpeechStream(this, this.#opts, options?.connOptions);\n this.#streams.add(new WeakRef(stream));\n return stream;\n }\n}\n\nexport class SpeechStream extends stt.SpeechStream {\n static readonly CLOSE_MSG = JSON.stringify({ type: 'Terminate' });\n\n #opts: STTOptions;\n #logger = log();\n #speechDurationInS = 0;\n #lastPreflightStartTime = 0;\n #pendingConfigMessages: Record<string, unknown>[] = [];\n #configMessagePending = new Future();\n #sessionId: string | null = null;\n #expiresAt: number | null = null;\n label = 'assemblyai.SpeechStream';\n\n constructor(stt: STT, opts: STTOptions, connOptions?: APIConnectOptions) {\n super(stt, opts.sampleRate, connOptions);\n this.#opts = opts;\n this.closed = false;\n }\n\n /**\n * The AssemblyAI session ID. Set when the WebSocket connection is established\n * (before any speech events). Null until the connection completes.\n * Share this with the AssemblyAI team when reporting issues.\n */\n get sessionId(): string | null {\n return this.#sessionId;\n }\n\n /**\n * Unix timestamp when the AssemblyAI session expires. Set alongside\n * {@link sessionId} when the WebSocket connection is established.\n */\n get expiresAt(): number | null {\n return this.#expiresAt;\n }\n\n updateOptions(opts: Partial<STTOptions>) {\n this.#opts = { ...this.#opts, ...opts };\n\n const configMsg: Record<string, unknown> = { type: 'UpdateConfiguration' };\n if (opts.prompt !== undefined) configMsg.prompt = opts.prompt;\n if (opts.agentContext !== undefined) configMsg.agent_context = opts.agentContext;\n if (opts.keytermsPrompt !== undefined) configMsg.keyterms_prompt = opts.keytermsPrompt;\n if (opts.maxTurnSilence !== undefined) configMsg.max_turn_silence = opts.maxTurnSilence;\n if (opts.minTurnSilence !== undefined) configMsg.min_turn_silence = opts.minTurnSilence;\n if (opts.endOfTurnConfidenceThreshold !== undefined) {\n configMsg.end_of_turn_confidence_threshold = opts.endOfTurnConfidenceThreshold;\n }\n if (opts.vadThreshold !== undefined) configMsg.vad_threshold = opts.vadThreshold;\n\n // Only send if any actual fields (besides `type`) were specified.\n if (Object.keys(configMsg).length > 1) {\n this.#pendingConfigMessages.push(configMsg);\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n }\n\n /**\n * Force-finalize the current turn immediately.\n */\n forceEndpoint() {\n this.#pendingConfigMessages.push({ type: 'ForceEndpoint' });\n if (!this.#configMessagePending.done) this.#configMessagePending.resolve();\n }\n\n // Deepgram-style reconnect loop around a single websocket lifetime.\n protected async run() {\n const maxRetry = 32;\n let retries = 0;\n\n while (!this.input.closed && !this.closed) {\n try {\n const ws = await this.#connectWS();\n await this.#runWS(ws);\n retries = 0;\n } catch (e) {\n if (!this.closed && !this.input.closed) {\n if (retries >= maxRetry) {\n throw new Error(`failed to connect to AssemblyAI after ${retries} attempts: ${e}`);\n }\n\n const retryDelaySeconds = Math.min(retries * 5, 10);\n retries++;\n\n this.#logger.warn(\n `failed to connect to AssemblyAI, retrying in ${retryDelaySeconds} seconds: ${e} (${retries}/${maxRetry})`,\n );\n await delay(retryDelaySeconds * 1000);\n } else {\n this.#logger.warn(\n `AssemblyAI disconnected, connection is closed: ${e} (inputClosed: ${this.input.closed}, isClosed: ${this.closed})`,\n );\n }\n }\n }\n\n this.closed = true;\n }\n\n async #connectWS(): Promise<WebSocket> {\n // Universal-3 Pro family models default both min and max silence to 100ms when unset.\n // When a mode preset is selected, leave them unset unless explicitly provided so the\n // server's per-mode silence tuning is not overridden by the latency-optimized default.\n let minSilence = this.#opts.minTurnSilence;\n let maxSilence = this.#opts.maxTurnSilence;\n if (isU3ProModel(this.#opts.speechModel)) {\n if (minSilence === undefined && this.#opts.mode === undefined) minSilence = 100;\n if (maxSilence === undefined) maxSilence = minSilence;\n }\n\n // Default language_detection to true for multilingual / u3-rt-pro-family models, false otherwise.\n const defaultLanguageDetection =\n this.#opts.speechModel.includes('multilingual') || isU3ProModel(this.#opts.speechModel);\n const languageDetection = this.#opts.languageDetection ?? defaultLanguageDetection;\n\n const liveConfig: Record<string, unknown> = {\n sample_rate: this.#opts.sampleRate,\n encoding: this.#opts.encoding,\n speech_model: this.#opts.speechModel,\n format_turns: this.#opts.formatTurns,\n end_of_turn_confidence_threshold: this.#opts.endOfTurnConfidenceThreshold,\n min_turn_silence: minSilence,\n max_turn_silence: maxSilence,\n keyterms_prompt:\n this.#opts.keytermsPrompt !== undefined && this.#opts.keytermsPrompt.length > 0\n ? JSON.stringify(this.#opts.keytermsPrompt)\n : undefined,\n language_detection: languageDetection,\n inactivity_timeout: this.#opts.inactivityTimeout,\n prompt: this.#opts.prompt,\n agent_context: this.#opts.agentContext,\n previous_context_n_turns: this.#opts.previousContextNTurns,\n vad_threshold: this.#opts.vadThreshold,\n speaker_labels: this.#opts.speakerLabels,\n max_speakers: this.#opts.maxSpeakers,\n domain: this.#opts.domain,\n voice_focus: this.#opts.voiceFocus,\n voice_focus_threshold: this.#opts.voiceFocusThreshold,\n mode: this.#opts.mode,\n };\n\n const url = new URL(`${this.#opts.baseUrl}/v3/ws`);\n // Python serializes booleans as the strings \"true\"/\"false\", so we mirror that.\n for (const [key, value] of Object.entries(liveConfig)) {\n if (value === undefined || value === null) continue;\n if (typeof value === 'boolean') {\n url.searchParams.append(key, value ? 'true' : 'false');\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n\n const ws = new WebSocket(url, {\n headers: {\n Authorization: this.#opts.apiKey!,\n 'Content-Type': 'application/json',\n 'User-Agent': 'AssemblyAI/1.0 (integration=Livekit)',\n },\n });\n\n await new Promise<void>((resolve, reject) => {\n ws.on('open', () => resolve());\n ws.on('error', (error) => reject(error));\n ws.on('close', (code) => reject(new Error(`WebSocket returned ${code}`)));\n });\n\n return ws;\n }\n\n async #runWS(ws: WebSocket) {\n let closing = false;\n const sessionController = new AbortController();\n\n // gets cancelled also when sendTask is complete\n const wsMonitor = Task.from(async (controller) => {\n const closed = new Promise<void>((_, reject) => {\n ws.once('close', (code, reason) => {\n if (!closing) {\n this.#logger.error(`WebSocket closed with code ${code}: ${reason}`);\n reject(new Error('WebSocket closed'));\n }\n });\n });\n\n await Promise.race([closed, waitForAbort(controller.signal)]);\n });\n\n const sendTask = async () => {\n const samplesPerBuffer = Math.floor((this.#opts.sampleRate * this.#opts.bufferSizeMs) / 1000);\n const audioStream = new AudioByteStream(this.#opts.sampleRate, 1, samplesPerBuffer);\n\n const abortPromise = waitForAbort(this.abortSignal);\n const sessionAbort = waitForAbort(sessionController.signal);\n\n try {\n while (!this.closed) {\n const result = await Promise.race([this.input.next(), abortPromise, sessionAbort]);\n\n if (result === undefined) return; // aborted\n if (result.done) break;\n\n const data = result.value;\n\n let frames: AudioFrame[];\n if (data === SpeechStream.FLUSH_SENTINEL) {\n frames = audioStream.flush();\n } else if (data.sampleRate === this.#opts.sampleRate && data.channels === 1) {\n // AssemblyAI expects mono PCM. The base SpeechStream only resamples\n // sample rate, so reject any frame that is not already downmixed.\n frames = audioStream.write(data.data.buffer as ArrayBuffer);\n } else {\n throw new Error('sample rate or channel count of frame does not match');\n }\n\n for (const frame of frames) {\n this.#speechDurationInS += frame.samplesPerChannel / frame.sampleRate;\n ws.send(frame.data.buffer);\n }\n }\n } finally {\n closing = true;\n try {\n ws.send(SpeechStream.CLOSE_MSG);\n } catch {\n // ignore — socket may already be closing\n }\n wsMonitor.cancel();\n }\n };\n\n let messageHandler: ((msg: RawData, isBinary: boolean) => void) | null = null;\n const listenTask = Task.from(async (controller) => {\n const listenMessage = new Promise<void>((resolve, reject) => {\n messageHandler = (msg, isBinary) => {\n if (isBinary) {\n this.#logger.error('unexpected binary message from AssemblyAI');\n return;\n }\n try {\n const json = JSON.parse(msg.toString()) as StreamEventMessage;\n this.#processStreamEvent(json);\n if (this.closed || closing) {\n resolve();\n }\n } catch (err) {\n this.#logger.error(`AssemblyAI: error processing message: ${msg}`);\n reject(err);\n }\n };\n ws.on('message', messageHandler);\n });\n\n await Promise.race([listenMessage, waitForAbort(controller.signal)]);\n });\n\n const configTask = Task.from(async (controller) => {\n // Drain any messages queued while the socket was reconnecting.\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n\n while (!controller.signal.aborted) {\n await Promise.race([this.#configMessagePending.await, waitForAbort(controller.signal)]);\n if (controller.signal.aborted) return;\n\n this.#configMessagePending = new Future();\n while (this.#pendingConfigMessages.length > 0) {\n const msg = this.#pendingConfigMessages.shift()!;\n ws.send(JSON.stringify(msg));\n }\n }\n });\n\n try {\n await Promise.all([sendTask(), listenTask.result, wsMonitor.result]);\n } finally {\n closing = true;\n sessionController.abort();\n listenTask.cancel();\n configTask.cancel();\n if (messageHandler) ws.off('message', messageHandler);\n try {\n ws.close();\n } catch {\n // ignore\n }\n }\n }\n\n #averageConfidence(words: Array<{ confidence?: number }>): number {\n if (words.length === 0) return 0;\n return words.reduce((sum, w) => sum + (w.confidence ?? 0), 0) / words.length;\n }\n\n #processStreamEvent(data: StreamEventMessage) {\n const messageType = data.type;\n\n if (messageType === 'Begin') {\n this.#sessionId = data.id ?? null;\n this.#expiresAt = data.expires_at ?? null;\n this.#logger.info(\n `AssemblyAI session started id=${this.#sessionId} expires_at=${this.#expiresAt}`,\n );\n return;\n }\n\n if (messageType === 'SpeechStarted') {\n this.queue.put({ type: stt.SpeechEventType.START_OF_SPEECH });\n return;\n }\n\n if (messageType === 'Termination') {\n this.#logger.debug(\n `AssemblyAI session terminated audio_duration=${data.audio_duration_seconds}s session_duration=${data.session_duration_seconds}s`,\n );\n return;\n }\n\n if (messageType !== 'Turn') {\n return;\n }\n\n const words = data.words ?? [];\n const endOfTurn = Boolean(data.end_of_turn);\n const turnIsFormatted = Boolean(data.turn_is_formatted);\n const utterance = data.utterance ?? '';\n const transcript = data.transcript ?? '';\n const language = normalizeLanguage(data.language_code ?? 'en');\n const metadata = speechDataMetadata(data);\n\n // Word timestamps are in milliseconds:\n // https://www.assemblyai.com/docs/api-reference/streaming-api/streaming-api#receive.receiveTurn.words\n const timedWords = words.map((word) =>\n createTimedString({\n text: word.text ?? '',\n startTime: (word.start ?? 0) / 1000 + this.startTimeOffset,\n endTime: (word.end ?? 0) / 1000 + this.startTimeOffset,\n confidence: word.confidence ?? 0,\n startTimeOffset: this.startTimeOffset,\n }),\n );\n\n let startTime = 0;\n let endTime = 0;\n let confidence = 0;\n\n // `words` are cumulative for the turn — emit as an interim transcript.\n if (timedWords.length > 0) {\n const interimText = timedWords.map((w) => w.text).join(' ');\n startTime = timedWords[0]!.startTime ?? 0;\n endTime = timedWords[timedWords.length - 1]!.endTime ?? 0;\n confidence = this.#averageConfidence(timedWords);\n\n this.queue.put({\n type: stt.SpeechEventType.INTERIM_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: interimText,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n ...(metadata ? { metadata } : {}),\n },\n ],\n });\n }\n\n // `utterance` is chunk-based (not cumulative) — emit as a preflight transcript\n // covering only the words since the last preflight.\n if (utterance) {\n if (this.#lastPreflightStartTime === 0) {\n this.#lastPreflightStartTime = startTime;\n }\n\n const utteranceWords = timedWords.filter(\n (w) => w.startTime !== undefined && w.startTime >= this.#lastPreflightStartTime,\n );\n const utteranceConfidence = this.#averageConfidence(utteranceWords);\n\n this.queue.put({\n type: stt.SpeechEventType.PREFLIGHT_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: utterance,\n startTime: this.#lastPreflightStartTime,\n endTime,\n confidence: utteranceConfidence,\n words: utteranceWords,\n ...(metadata ? { metadata } : {}),\n },\n ],\n });\n this.#lastPreflightStartTime = endTime;\n }\n\n // End-of-turn: emit FINAL_TRANSCRIPT + END_OF_SPEECH.\n // If the user asked for formatted turns, wait for a formatted final.\n const waitingForFormatted = this.#opts.formatTurns === true && !turnIsFormatted;\n if (endOfTurn && !waitingForFormatted) {\n this.queue.put({\n type: stt.SpeechEventType.FINAL_TRANSCRIPT,\n alternatives: [\n {\n language,\n text: transcript,\n startTime,\n endTime,\n confidence,\n words: timedWords,\n ...(metadata ? { metadata } : {}),\n },\n ],\n });\n\n this.queue.put({ type: stt.SpeechEventType.END_OF_SPEECH });\n if (this.#speechDurationInS > 0) {\n this.queue.put({\n type: stt.SpeechEventType.RECOGNITION_USAGE,\n // Propagate the AssemblyAI session id as the request id so metrics\n // can be correlated back to a specific connection, mirroring how\n // Deepgram surfaces its `request_id`.\n requestId: this.#sessionId ?? undefined,\n recognitionUsage: {\n audioDuration: this.#speechDurationInS,\n },\n });\n this.#speechDurationInS = 0;\n this.#lastPreflightStartTime = 0;\n }\n }\n }\n}\n"],"mappings":"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","names":["stt"]}
|