From 18ecdfa154c0b1c3f49e1af23b6ad8ef439d75e2 Mon Sep 17 00:00:00 2001 From: Chase Fagen Date: Fri, 26 Jun 2026 11:27:19 +0100 Subject: [PATCH] Add AssemblyAI inactivity timeout option --- .changeset/assemblyai-inactivity-timeout.md | 5 +++ plugins/assemblyai/src/stt.test.ts | 50 +++++++++++++++++++++ plugins/assemblyai/src/stt.ts | 6 +++ 3 files changed, 61 insertions(+) create mode 100644 .changeset/assemblyai-inactivity-timeout.md diff --git a/.changeset/assemblyai-inactivity-timeout.md b/.changeset/assemblyai-inactivity-timeout.md new file mode 100644 index 000000000..059d30c28 --- /dev/null +++ b/.changeset/assemblyai-inactivity-timeout.md @@ -0,0 +1,5 @@ +--- +'@livekit/agents-plugin-assemblyai': patch +--- + +Add AssemblyAI streaming `inactivityTimeout` support. diff --git a/plugins/assemblyai/src/stt.test.ts b/plugins/assemblyai/src/stt.test.ts index 7d8dc785c..f4872caf5 100644 --- a/plugins/assemblyai/src/stt.test.ts +++ b/plugins/assemblyai/src/stt.test.ts @@ -3,9 +3,33 @@ // SPDX-License-Identifier: Apache-2.0 import { VAD } from '@livekit/agents-plugin-silero'; import { stt } from '@livekit/agents-plugins-test'; +import { once } from 'node:events'; +import type { AddressInfo } from 'node:net'; import { describe, expect, it } from 'vitest'; +import { WebSocketServer } from 'ws'; import { STT } from './stt.js'; +async function startWebSocketServer() { + const wss = new WebSocketServer({ host: '127.0.0.1', port: 0 }); + await once(wss, 'listening'); + const address = wss.address() as AddressInfo; + return { wss, baseUrl: `ws://127.0.0.1:${address.port}` }; +} + +async function closeWebSocketServer(wss: WebSocketServer): Promise { + for (const client of wss.clients) client.close(); + await new Promise((resolve) => wss.close(() => resolve())); +} + +async function waitUntil(predicate: () => boolean, timeoutMs = 1000): Promise { + const startedAt = Date.now(); + while (Date.now() - startedAt < timeoutMs) { + if (predicate()) return; + await new Promise((resolve) => setTimeout(resolve, 10)); + } + throw new Error('timed out waiting for condition'); +} + describe('AssemblyAI options', () => { it('accepts u3-rt-pro-beta-1', () => { const stt = new STT({ apiKey: 'test-key', speechModel: 'u3-rt-pro-beta-1' }); @@ -47,6 +71,32 @@ describe('AssemblyAI options', () => { }), ).toThrow(/previousContextNTurns/); }); + + it('forwards inactivity timeout to the streaming query', async () => { + const { wss, baseUrl } = await startWebSocketServer(); + let requestUrl = ''; + + wss.on('connection', (_ws, req) => { + requestUrl = req.url ?? ''; + }); + + try { + const stream = new STT({ + apiKey: 'test-key', + baseUrl, + inactivityTimeout: 45, + }).stream(); + + await waitUntil(() => requestUrl !== ''); + stream.close(); + + const url = new URL(`ws://127.0.0.1${requestUrl}`); + expect(url.pathname).toBe('/v3/ws'); + expect(url.searchParams.get('inactivity_timeout')).toBe('45'); + } finally { + await closeWebSocketServer(wss); + } + }); }); const hasAssemblyAIApiKey = Boolean(process.env.ASSEMBLYAI_API_KEY); diff --git a/plugins/assemblyai/src/stt.ts b/plugins/assemblyai/src/stt.ts index b504ac59f..dca019579 100644 --- a/plugins/assemblyai/src/stt.ts +++ b/plugins/assemblyai/src/stt.ts @@ -65,6 +65,11 @@ export interface STTOptions { encoding: STTEncoding; speechModel: STTModels; languageDetection?: boolean; + /** + * Session inactivity timeout in seconds. AssemblyAI accepts integer values + * from 5 to 3600; when unset, no inactivity timeout is applied. + */ + inactivityTimeout?: number; endOfTurnConfidenceThreshold?: number; /** Minimum silence (ms) before a confident end-of-turn is finalized. */ minTurnSilence?: number; @@ -322,6 +327,7 @@ export class SpeechStream extends stt.SpeechStream { ? JSON.stringify(this.#opts.keytermsPrompt) : undefined, language_detection: languageDetection, + inactivity_timeout: this.#opts.inactivityTimeout, prompt: this.#opts.prompt, agent_context: this.#opts.agentContext, previous_context_n_turns: this.#opts.previousContextNTurns,