import assert from "node:assert/strict"; import { createServer } from "node:http"; import { test } from "node:test"; import plugin from "./dist/index.js"; async function waitFor(predicate, timeoutMs = 2000) { const deadline = Date.now() + timeoutMs; while (!predicate()) { if (Date.now() >= deadline) throw new Error("timed out waiting for condition"); await new Promise((resolve) => setTimeout(resolve, 10)); } } test("dictation registers separately and sends G.711 audio to Athena Qwen3-ASR", async () => { let transcription; plugin.register({ registerRealtimeTranscriptionProvider: (value) => { transcription = value; }, registerRealtimeVoiceProvider: () => {}, registerHttpRoute: () => {}, }); assert.equal(transcription.id, "athena-talk"); let uploads = 0; const server = createServer(async (req, res) => { uploads += 1; assert.equal(req.url, "/v1/audio/transcriptions"); assert.equal(req.headers.authorization, "Bearer test-key"); const form = await new Request("http://localhost", { method: "POST", headers: { "Content-Type": req.headers["content-type"] }, body: req, duplex: "half", }).formData(); const wav = Buffer.from(await form.get("file").arrayBuffer()); assert.equal(wav.toString("ascii", 0, 4), "RIFF"); assert.equal(wav.readUInt32LE(24), 8000); assert.equal(wav.readUInt16LE(34), 16); assert.equal(wav.length, 48); assert.equal(form.get("language"), "de"); assert.equal(form.get("model"), "qwen3-asr"); res.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify({ text: "Hallo Athena" })); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const cfg = { talk: { realtime: { providers: { "athena-talk": { baseUrl: `http://127.0.0.1:${server.address().port}/v1`, apiKey: "test-key", language: "de", } } } } }; const providerConfig = transcription.resolveConfig({ cfg, rawConfig: {} }); assert.equal(transcription.isConfigured({ cfg, providerConfig }), true); try { const result = new Promise((resolve, reject) => { const session = transcription.createSession({ cfg, providerConfig, onTranscript: resolve, onError: reject, }); session.connect().then(() => { session.sendAudio(Buffer.from([0xff, 0xff])); session.close(); }, reject); }); assert.equal(await result, "Hallo Athena"); assert.equal(uploads, 1); } finally { server.closeAllConnections(); await new Promise((resolve) => server.close(resolve)); } }); test("dictation reuses only a TTS key for the same Athena origin", () => { let transcription; plugin.register({ registerRealtimeTranscriptionProvider: (value) => { transcription = value; }, registerRealtimeVoiceProvider: () => {}, registerHttpRoute: () => {}, }); const base = { talk: { realtime: { providers: { "athena-talk": { modelProvider: "llama-cpp" } } } }, models: { providers: { "llama-cpp": { baseUrl: "http://athena:8081/v1" } } }, }; const withTts = (baseUrl) => ({ ...base, tts: { providers: { openai: { baseUrl, apiKey: "shared-key", } } } }); assert.equal(transcription.resolveConfig({ cfg: withTts("http://athena:8081/v1"), rawConfig: {}, }).apiKey, "shared-key"); assert.equal(transcription.resolveConfig({ cfg: withTts("http://other:8081/v1"), rawConfig: {}, }).apiKey, ""); }); test("long dictation is transcribed incrementally before the recording closes", async () => { let transcription; plugin.register({ registerRealtimeTranscriptionProvider: (value) => { transcription = value; }, registerRealtimeVoiceProvider: () => {}, registerHttpRoute: () => {}, }); const answers = [ "Dies ist ein langer Abschnitt", "langer Abschnitt mit einer Fortsetzung", "einer Fortsetzung und einem Ende.", ]; let uploads = 0; const server = createServer(async (req, res) => { const index = uploads++; const form = await new Request("http://localhost", { method: "POST", headers: { "Content-Type": req.headers["content-type"] }, body: req, duplex: "half", }).formData(); const wav = Buffer.from(await form.get("file").arrayBuffer()); assert.equal(wav.readUInt32LE(24), 8000); if (index > 0) assert.ok(String(form.get("prompt") || "").length > 0); res.writeHead(200, { "Content-Type": "application/json" }) .end(JSON.stringify({ text: answers[index] })); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const cfg = { talk: { realtime: { providers: { "athena-talk": { baseUrl: `http://127.0.0.1:${server.address().port}/v1`, language: "de", } } } } }; const providerConfig = transcription.resolveConfig({ cfg, rawConfig: {} }); const transcripts = []; const errors = []; try { const session = transcription.createSession({ cfg, providerConfig, onTranscript: (text) => transcripts.push(text), onError: (error) => errors.push(error), }); await session.connect(); // Six seconds start the first request while dictation is still active. session.sendAudio(Buffer.alloc(48_000, 0xff)); await waitFor(() => uploads === 1); assert.deepEqual(transcripts, []); // Another 5.5 seconds form the next overlapping segment. The remaining // 2 seconds are finalized only when the user stops dictation. session.sendAudio(Buffer.alloc(44_000, 0xff)); await waitFor(() => uploads === 2); session.sendAudio(Buffer.alloc(16_000, 0xff)); session.close(); await waitFor(() => transcripts.length === 3); assert.deepEqual(transcripts, [ "Dies ist ein langer Abschnitt", "mit einer Fortsetzung", "und einem Ende.", ]); assert.equal(uploads, 3); assert.deepEqual(errors, []); } finally { server.closeAllConnections(); await new Promise((resolve) => server.close(resolve)); } });