import { createServer } from "node:http"; import type { AddressInfo } from "node:net"; /** * Stands in for the local Kokoro and Piper speech servers so onboarding's voice step never * depends on whatever happens to listen on the default ports of the machine running the suite. */ export async function startFakeKokoro(): Promise<{ baseUrl: string; close: () => Promise }> { const server = createServer((req, res) => { if (req.method === "GET" && req.url === "/v1/audio/voices") { res.writeHead(200, { "content-type": "application/json" }); // An empty list makes the adapter fall back to its curated presets. res.end(JSON.stringify({ voices: [] })); return; } if (req.method === "POST" && req.url === "/v1/audio/speech") { req.resume(); req.on("end", () => { // An MPEG frame header is enough for callers that only check the content type. res.writeHead(200, { "content-type": "audio/mpeg" }); res.end(Buffer.from([0xff, 0xfb, 0x90, 0x64, 0, 0, 0, 0])); }); return; } if (req.method === "GET" && req.url === "/voices") { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ "de_DE-thorsten-high": {}, "de_DE-eva_k-x_low": {} })); return; } if (req.method === "POST" && req.url === "/synthesize") { req.resume(); req.on("end", () => { res.writeHead(200, { "content-type": "audio/wav" }); res.end(Buffer.from("RIFF\0\0\0\0WAVE")); }); return; } res.writeHead(404).end(); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const { port } = server.address() as AddressInfo; return { baseUrl: `http://127.0.0.1:${port}`, close: () => new Promise((resolve) => server.close(() => resolve())), }; }