@kuralle-syrinx/grok 4.2.0 → 4.4.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/src/tts.test.ts DELETED
@@ -1,160 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
-
3
- import { afterEach, describe, expect, it } from "vitest";
4
- import { WebSocketServer, type WebSocket } from "ws";
5
- import {
6
- PipelineBusImpl,
7
- Route,
8
- type TextToSpeechAudioPacket,
9
- type TextToSpeechEndPacket,
10
- } from "@kuralle-syrinx/core";
11
-
12
- import { bytesToBase64 } from "@kuralle-syrinx/realtime";
13
- import { GrokTTSPlugin } from "./tts.js";
14
-
15
- let servers: WebSocketServer[] = [];
16
-
17
- afterEach(async () => {
18
- await Promise.all(
19
- servers.splice(0).map(
20
- (server) =>
21
- new Promise<void>((resolve) => {
22
- for (const client of server.clients) client.terminate();
23
- server.close(() => resolve());
24
- }),
25
- ),
26
- );
27
- });
28
-
29
- async function createLocalServer(
30
- onConnection: (socket: WebSocket, requestUrl: string, authHeader: string) => void,
31
- ): Promise<string> {
32
- const server = await new Promise<WebSocketServer>((resolve) => {
33
- let next: WebSocketServer;
34
- next = new WebSocketServer({ port: 0 }, () => resolve(next));
35
- });
36
- servers.push(server);
37
- server.on("connection", (socket, request) => {
38
- const header = request.headers["authorization"];
39
- onConnection(
40
- socket,
41
- request.url ?? "",
42
- Array.isArray(header) ? header[0] ?? "" : header ?? "",
43
- );
44
- });
45
- const address = server.address();
46
- if (!address || typeof address === "string") throw new Error("Expected TCP server address");
47
- return `ws://127.0.0.1:${address.port}/v1/tts`;
48
- }
49
-
50
- async function waitForCondition(predicate: () => boolean, timeoutMs = 1000): Promise<void> {
51
- const startedAt = Date.now();
52
- while (Date.now() - startedAt < timeoutMs) {
53
- if (predicate()) return;
54
- await new Promise((resolve) => setTimeout(resolve, 10));
55
- }
56
- throw new Error("Timed out waiting for Grok TTS test condition");
57
- }
58
-
59
- describe("GrokTTSPlugin", () => {
60
- it("streams text.delta and maps audio.delta to tts.audio", async () => {
61
- const received: Array<Record<string, unknown>> = [];
62
- const endpointUrl = await createLocalServer((socket, requestUrl, authHeader) => {
63
- expect(requestUrl).toContain("voice=eve");
64
- expect(requestUrl).toContain("codec=pcm");
65
- expect(requestUrl).toContain("sample_rate=16000");
66
- expect(authHeader).toBe("Bearer test-xai-key");
67
- socket.on("message", (data) => {
68
- const msg = JSON.parse(data.toString()) as Record<string, unknown>;
69
- received.push(msg);
70
- if (msg["type"] === "text.delta") {
71
- socket.send(JSON.stringify({
72
- type: "audio.delta",
73
- delta: bytesToBase64(new Uint8Array([1, 2, 3, 4])),
74
- }));
75
- }
76
- if (msg["type"] === "text.done") {
77
- socket.send(JSON.stringify({ type: "audio.done" }));
78
- }
79
- });
80
- });
81
-
82
- const bus = new PipelineBusImpl();
83
- const started = bus.start();
84
- const plugin = new GrokTTSPlugin();
85
- const audio: TextToSpeechAudioPacket[] = [];
86
- const ends: TextToSpeechEndPacket[] = [];
87
- bus.on("tts.audio", (pkt) => {
88
- audio.push(pkt as TextToSpeechAudioPacket);
89
- });
90
- bus.on("tts.end", (pkt) => {
91
- ends.push(pkt as TextToSpeechEndPacket);
92
- });
93
-
94
- await plugin.initialize(bus, {
95
- api_key: "test-xai-key",
96
- endpoint_url: endpointUrl,
97
- voice_id: "eve",
98
- sample_rate: 16000,
99
- });
100
- bus.push(Route.Main, {
101
- kind: "tts.text",
102
- contextId: "turn-1",
103
- timestampMs: Date.now(),
104
- text: "Hello there.",
105
- });
106
- bus.push(Route.Main, { kind: "tts.done", contextId: "turn-1", timestampMs: Date.now() });
107
- await waitForCondition(() => ends.length >= 1);
108
-
109
- expect(received).toEqual([
110
- expect.objectContaining({ type: "text.delta", delta: "Hello there." }),
111
- expect.objectContaining({ type: "text.done" }),
112
- ]);
113
- expect(audio).toEqual([
114
- expect.objectContaining({
115
- contextId: "turn-1",
116
- sampleRateHz: 16000,
117
- audio: new Uint8Array([1, 2, 3, 4]),
118
- provider: expect.objectContaining({ name: "grok", model: "eve" }),
119
- }),
120
- ]);
121
- expect(ends).toEqual([expect.objectContaining({ contextId: "turn-1" })]);
122
-
123
- await plugin.close();
124
- bus.stop();
125
- await started;
126
- });
127
-
128
- it("sends text.clear on interrupt", async () => {
129
- const received: Array<Record<string, unknown>> = [];
130
- const endpointUrl = await createLocalServer((socket) => {
131
- socket.on("message", (data) => {
132
- received.push(JSON.parse(data.toString()) as Record<string, unknown>);
133
- });
134
- });
135
-
136
- const bus = new PipelineBusImpl();
137
- const started = bus.start();
138
- const plugin = new GrokTTSPlugin();
139
-
140
- await plugin.initialize(bus, { api_key: "test-xai-key", endpoint_url: endpointUrl });
141
- bus.push(Route.Main, {
142
- kind: "tts.text",
143
- contextId: "turn-x",
144
- timestampMs: Date.now(),
145
- text: "Interrupt me.",
146
- });
147
- await waitForCondition(() => received.some((m) => m["type"] === "text.delta"));
148
- bus.push(Route.Critical, { kind: "interrupt.tts", contextId: "turn-x", timestampMs: Date.now() });
149
- await waitForCondition(() => received.some((m) => m["type"] === "text.clear"));
150
-
151
- expect(received).toEqual([
152
- expect.objectContaining({ type: "text.delta", delta: "Interrupt me." }),
153
- expect.objectContaining({ type: "text.clear" }),
154
- ]);
155
-
156
- await plugin.close();
157
- bus.stop();
158
- await started;
159
- });
160
- });
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "lib": ["ES2022"],
7
- "strict": true,
8
- "noUncheckedIndexedAccess": true,
9
- "noImplicitReturns": true,
10
- "declaration": true,
11
- "declarationMap": true,
12
- "sourceMap": true,
13
- "outDir": "./dist",
14
- "rootDir": "./src",
15
- "esModuleInterop": true,
16
- "skipLibCheck": true,
17
- "forceConsistentCasingInFileNames": true
18
- },
19
- "include": ["src/**/*.ts"],
20
- "exclude": ["node_modules", "dist"]
21
- }