@kuralle-syrinx/deepgram 4.1.0 → 4.2.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/package.json +23 -4
- package/src/stt.test.ts +66 -0
- package/src/stt.ts +41 -3
package/package.json
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kuralle-syrinx/deepgram",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"
|
|
5
|
+
"description": "Deepgram adapters for Syrinx — nova-3 STT, Flux turn-aware STT (semantic end-of-turn), and Aura TTS",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"voice",
|
|
8
|
+
"voice-agent",
|
|
9
|
+
"speech",
|
|
10
|
+
"syrinx",
|
|
11
|
+
"deepgram",
|
|
12
|
+
"speech-to-text",
|
|
13
|
+
"text-to-speech"
|
|
14
|
+
],
|
|
6
15
|
"license": "MIT",
|
|
16
|
+
"homepage": "https://github.com/kuralle/syrinx#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/kuralle/syrinx.git",
|
|
20
|
+
"directory": "packages/deepgram"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/kuralle/syrinx/issues"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
7
26
|
"main": "./src/index.ts",
|
|
8
27
|
"types": "./src/index.ts",
|
|
9
28
|
"exports": {
|
|
@@ -12,8 +31,8 @@
|
|
|
12
31
|
"./tts": "./src/tts.ts"
|
|
13
32
|
},
|
|
14
33
|
"dependencies": {
|
|
15
|
-
"@kuralle-syrinx/core": "4.
|
|
16
|
-
"@kuralle-syrinx/ws": "4.
|
|
34
|
+
"@kuralle-syrinx/core": "4.2.0",
|
|
35
|
+
"@kuralle-syrinx/ws": "4.2.0"
|
|
17
36
|
},
|
|
18
37
|
"devDependencies": {
|
|
19
38
|
"@types/ws": "^8.5.0",
|
package/src/stt.test.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
Route,
|
|
8
8
|
type ConversationMetricPacket,
|
|
9
9
|
type SttErrorPacket,
|
|
10
|
+
type SttInterimPacket,
|
|
11
|
+
type SttPartialPacket,
|
|
10
12
|
type SttResultPacket,
|
|
11
13
|
} from "@kuralle-syrinx/core";
|
|
12
14
|
|
|
@@ -59,6 +61,70 @@ async function waitForValue<T>(items: T[], value: T): Promise<void> {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
describe("DeepgramSTTPlugin", () => {
|
|
64
|
+
it("emits stt.partial with word timings alongside unchanged stt.interim", async () => {
|
|
65
|
+
const endpointUrl = await createLocalServer((socket) => {
|
|
66
|
+
socket.on("message", (data, isBinary) => {
|
|
67
|
+
if (!isBinary) return;
|
|
68
|
+
socket.send(JSON.stringify({
|
|
69
|
+
is_final: false,
|
|
70
|
+
channel: {
|
|
71
|
+
alternatives: [{
|
|
72
|
+
transcript: "hello there",
|
|
73
|
+
confidence: 0.91,
|
|
74
|
+
words: [
|
|
75
|
+
{ word: "hello", start: 0.12, end: 0.48, confidence: 0.95 },
|
|
76
|
+
{ word: "there", start: 0.5, end: 0.82, confidence: 0.88 },
|
|
77
|
+
],
|
|
78
|
+
}],
|
|
79
|
+
},
|
|
80
|
+
}));
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
const bus = new PipelineBusImpl();
|
|
84
|
+
const started = startBus(bus);
|
|
85
|
+
const plugin = new DeepgramSTTPlugin();
|
|
86
|
+
const interims: SttInterimPacket[] = [];
|
|
87
|
+
const partials: SttPartialPacket[] = [];
|
|
88
|
+
bus.on("stt.interim", (pkt) => {
|
|
89
|
+
interims.push(pkt as SttInterimPacket);
|
|
90
|
+
});
|
|
91
|
+
bus.on("stt.partial", (pkt) => {
|
|
92
|
+
partials.push(pkt as SttPartialPacket);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
await plugin.initialize(bus, {
|
|
96
|
+
api_key: "test",
|
|
97
|
+
endpoint_url: endpointUrl,
|
|
98
|
+
sample_rate: 16000,
|
|
99
|
+
});
|
|
100
|
+
bus.push(Route.Main, {
|
|
101
|
+
kind: "stt.audio",
|
|
102
|
+
contextId: "turn-1",
|
|
103
|
+
timestampMs: Date.now(),
|
|
104
|
+
audio: new Uint8Array(640),
|
|
105
|
+
});
|
|
106
|
+
await waitFor(partials);
|
|
107
|
+
|
|
108
|
+
expect(interims).toEqual([
|
|
109
|
+
expect.objectContaining({ kind: "stt.interim", contextId: "turn-1", text: "hello there" }),
|
|
110
|
+
]);
|
|
111
|
+
expect(partials).toEqual([
|
|
112
|
+
expect.objectContaining({
|
|
113
|
+
kind: "stt.partial",
|
|
114
|
+
contextId: "turn-1",
|
|
115
|
+
text: "hello there",
|
|
116
|
+
wordTimings: [
|
|
117
|
+
{ word: "hello", startMs: 120, endMs: 480, confidence: 0.95 },
|
|
118
|
+
{ word: "there", startMs: 500, endMs: 820, confidence: 0.88 },
|
|
119
|
+
],
|
|
120
|
+
}),
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
await plugin.close();
|
|
124
|
+
bus.stop();
|
|
125
|
+
await started;
|
|
126
|
+
});
|
|
127
|
+
|
|
62
128
|
it("uses provider KeepAlive while idle and CloseStream on shutdown", async () => {
|
|
63
129
|
const controlMessages: string[] = [];
|
|
64
130
|
const endpointUrl = await createLocalServer((socket) => {
|
package/src/stt.ts
CHANGED
|
@@ -355,7 +355,7 @@ export class DeepgramSTTPlugin implements VoicePlugin {
|
|
|
355
355
|
this.pushTurnComplete(providerContextId);
|
|
356
356
|
}
|
|
357
357
|
} else {
|
|
358
|
-
this.pushInterim(transcript, providerContextId);
|
|
358
|
+
this.pushInterim(transcript, providerContextId, alt);
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
|
|
@@ -512,12 +512,25 @@ export class DeepgramSTTPlugin implements VoicePlugin {
|
|
|
512
512
|
}
|
|
513
513
|
|
|
514
514
|
/** Emit interim transcript for real-time display. */
|
|
515
|
-
private pushInterim(
|
|
515
|
+
private pushInterim(
|
|
516
|
+
transcript: string,
|
|
517
|
+
contextId = this.currentContextId,
|
|
518
|
+
alt: Record<string, unknown> | null = null,
|
|
519
|
+
): void {
|
|
520
|
+
const timestampMs = Date.now();
|
|
516
521
|
this.bus?.push(Route.Main, {
|
|
517
522
|
kind: "stt.interim",
|
|
518
523
|
contextId,
|
|
519
|
-
timestampMs
|
|
524
|
+
timestampMs,
|
|
525
|
+
text: transcript,
|
|
526
|
+
});
|
|
527
|
+
const wordTimings = mapProviderWordTimings(alt);
|
|
528
|
+
this.bus?.push(Route.Main, {
|
|
529
|
+
kind: "stt.partial",
|
|
530
|
+
contextId,
|
|
531
|
+
timestampMs,
|
|
520
532
|
text: transcript,
|
|
533
|
+
...(wordTimings ? { wordTimings } : {}),
|
|
521
534
|
});
|
|
522
535
|
}
|
|
523
536
|
|
|
@@ -751,6 +764,31 @@ async function defaultSocketFactory(): Promise<SocketFactory> {
|
|
|
751
764
|
return mod.createNodeWsSocket;
|
|
752
765
|
}
|
|
753
766
|
|
|
767
|
+
function mapProviderWordTimings(
|
|
768
|
+
alt: Record<string, unknown> | null,
|
|
769
|
+
): ReadonlyArray<{ word: string; startMs: number; endMs: number; confidence: number }> | undefined {
|
|
770
|
+
if (!alt) return undefined;
|
|
771
|
+
const words = alt["words"];
|
|
772
|
+
if (!Array.isArray(words)) return undefined;
|
|
773
|
+
const timings: Array<{ word: string; startMs: number; endMs: number; confidence: number }> = [];
|
|
774
|
+
for (const entry of words) {
|
|
775
|
+
if (!entry || typeof entry !== "object") continue;
|
|
776
|
+
const row = entry as Record<string, unknown>;
|
|
777
|
+
const word = row["word"];
|
|
778
|
+
const start = row["start"];
|
|
779
|
+
const end = row["end"];
|
|
780
|
+
const confidence = row["confidence"];
|
|
781
|
+
if (typeof word !== "string" || typeof start !== "number" || typeof end !== "number") continue;
|
|
782
|
+
timings.push({
|
|
783
|
+
word,
|
|
784
|
+
startMs: start * 1000,
|
|
785
|
+
endMs: end * 1000,
|
|
786
|
+
confidence: typeof confidence === "number" ? confidence : 0,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
return timings.length > 0 ? timings : undefined;
|
|
790
|
+
}
|
|
791
|
+
|
|
754
792
|
function providerAlternative(msg: Record<string, unknown>): Record<string, unknown> | null {
|
|
755
793
|
const channel = msg["channel"];
|
|
756
794
|
if (!channel || typeof channel !== "object") return null;
|