Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bargein-default-threshold-drop-http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@livekit/agents': patch
---

Adaptive interruption detection now omits the threshold from `session.create` unless the user explicitly overrides it, letting the gateway apply its fetched default (surfaced via `default_threshold` on `session.created`). The HTTP transport has been dropped — detection always connects over WebSocket and always requires LiveKit credentials, and its base URL now defaults from `LIVEKIT_INFERENCE_URL` instead of `LIVEKIT_REMOTE_EOT_URL`. Inference requests also send an `X-LiveKit-Worker-Token` header when `LIVEKIT_WORKER_TOKEN` is set (hosted agents); a token supplied via the `--worker-token` CLI flag is now re-exported into the environment so forked job subprocesses inherit it and include the header. The `X-LiveKit-Agent-Id` header is now only attached once the room is connected to avoid leaking an unset local-participant SID. The interruption WebSocket is now closed deterministically on stream teardown (including error and cancel paths) instead of only on graceful completion — previously an orphaned socket leaked per session/activity and accumulated for the worker's lifetime. Mid-session threshold/duration changes via `updateOptions` now reconnect the WebSocket in place rather than closing it and letting the next send error the stream — so option changes no longer consume a failover retry (previously enough updates in a session could exhaust the retry budget and stop interruption detection).
58 changes: 58 additions & 0 deletions agents/src/inference/interruption/_mock_ws.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2026 LiveKit, Inc.
//
// SPDX-License-Identifier: Apache-2.0
import { EventEmitter } from 'node:events';

/**
* Minimal stand-in for the `ws` WebSocket, used to drive the interruption transport in tests.
*
* Lives in its own module (rather than inline in each test) so the `vi.mock('ws')` factory can
* `await import()` it without a top-level await — the tsup build transpiles test files to CJS,
* which does not support top-level await.
*/
export class MockWebSocket extends EventEmitter {
static OPEN = 1;
static instances: MockWebSocket[] = [];

readyState = 0; // CONNECTING
readonly sent: unknown[] = [];
terminated = false;

constructor(
public url: string,
public opts: unknown,
) {
super();
MockWebSocket.instances.push(this);
}

send(data: unknown): void {
this.sent.push(data);
}

close(): void {
this.readyState = 3; // CLOSED
this.emit('close', 1000, Buffer.from(''));
}

terminate(): void {
this.terminated = true;
this.readyState = 3;
}

/** Simulate a successful upgrade. */
simulateOpen(): void {
this.readyState = MockWebSocket.OPEN;
this.emit('open');
}

/** Simulate the server rejecting the upgrade with an HTTP status. */
simulateUnexpectedResponse(statusCode: number): void {
this.emit('unexpected-response', {}, { statusCode });
}

/** Simulate a server message frame carrying a JSON payload. */
simulateMessage(payload: unknown): void {
this.emit('message', Buffer.from(JSON.stringify(payload)));
}
}
8 changes: 4 additions & 4 deletions agents/src/inference/interruption/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import type { ApiConnectOptions } from './interruption_stream.js';
import type { InterruptionOptions } from './types.js';

export const MIN_INTERRUPTION_DURATION_IN_S = 0.025 * 2; // 25ms per frame, 2 consecutive frames
export const THRESHOLD = 0.5;
export const MAX_AUDIO_DURATION_IN_S = 3.0;
export const AUDIO_PREFIX_DURATION_IN_S = 0.5;
export const AUDIO_PREFIX_DURATION_IN_S = 1.0;
export const DETECTION_INTERVAL_IN_S = 0.1;
export const REMOTE_INFERENCE_TIMEOUT_IN_S = 0.7;
export const SAMPLE_RATE = 16000;
Expand Down Expand Up @@ -36,12 +35,13 @@ export function intervalForRetry(
}

// env-derived fields are resolved in the constructor, not at module load.
// `threshold` is intentionally omitted: when the user does not override it, it stays undefined
// so the server applies its fetched default.
export const interruptionOptionDefaults: Omit<
InterruptionOptions,
'baseUrl' | 'useProxy' | 'apiKey' | 'apiSecret'
'baseUrl' | 'apiKey' | 'apiSecret' | 'threshold'
> = {
sampleRate: SAMPLE_RATE,
threshold: THRESHOLD,
minFrames: Math.ceil(MIN_INTERRUPTION_DURATION_IN_S * FRAMES_PER_SECOND),
maxAudioDurationInS: MAX_AUDIO_DURATION_IN_S,
audioPrefixDurationInS: AUDIO_PREFIX_DURATION_IN_S,
Expand Down
208 changes: 0 additions & 208 deletions agents/src/inference/interruption/http_transport.ts

This file was deleted.

Loading
Loading