Summary
@livekit/agents exposes LIVEKIT_INFERENCE_API_KEY / LIVEKIT_INFERENCE_API_SECRET so users with a self-hosted SFU and a separate LK Cloud project for inference can authenticate against the inference gateway with different credentials. This works correctly for inference/llm.ts, inference/tts.ts and inference/stt.ts, but is broken in inference/interruption/interruption_detector.ts: when LIVEKIT_API_KEY is set (e.g. SFU creds), LIVEKIT_INFERENCE_API_KEY is silently ignored and the SDK signs the inference JWT with the SFU credentials, causing WebSocket connection rejected with status 401 and a fallback to VAD-based interruption.
Root cause
In agents/src/inference/interruption/defaults.ts the interruptionOptionDefaults object is initialised at module load with:
```ts
apiKey: process.env.LIVEKIT_API_KEY || "",
apiSecret: process.env.LIVEKIT_API_SECRET || "",
```
Then in agents/src/inference/interruption/interruption_detector.ts:
```ts
const { apiKey, apiSecret, ... } = { ...interruptionOptionDefaults, ...options };
// ...
lkApiKey = apiKey ?? process.env.LIVEKIT_INFERENCE_API_KEY ?? process.env.LIVEKIT_API_KEY ?? '';
```
Because apiKey already received process.env.LIVEKIT_API_KEY from the defaults, the ?? chain never falls back to LIVEKIT_INFERENCE_API_KEY.
The other three inference plugins use the correct precedence with ||:
```ts
// llm.ts, tts.ts, stt.ts
const lkApiKey = apiKey || process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY;
```
Reproduction
- Self-host the LiveKit SFU (creds A).
- Create an LK Cloud project and grab a separate set of creds (creds B).
- Set both pairs in the agent environment:
```
LIVEKIT_URL=ws://my-self-hosted:7880
LIVEKIT_API_KEY=A_key
LIVEKIT_API_SECRET=A_secret
LIVEKIT_INFERENCE_API_KEY=B_key
LIVEKIT_INFERENCE_API_SECRET=B_secret
```
- Configure `interruption: { mode: 'adaptive' }` and place a call.
- Worker registers fine against the SFU, but on the first overlap the adaptive detector fails with `WebSocket connection rejected with status 401` and falls back to VAD.
Suggested fix
Align defaults.ts with the precedence used by the other inference plugins:
```ts
apiKey: process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY || "",
apiSecret: process.env.LIVEKIT_INFERENCE_API_SECRET || process.env.LIVEKIT_API_SECRET || "",
```
Or alternatively keep defaults.ts neutral (empty strings) and let the ?? chain in interruption_detector.ts actually fall through to the env vars.
Environment
- `@livekit/agents` 1.3.0
- Node 24
- LK Cloud project for inference; self-hosted SFU (`livekit/livekit-server:v1.11.0`)
Summary
@livekit/agentsexposesLIVEKIT_INFERENCE_API_KEY/LIVEKIT_INFERENCE_API_SECRETso users with a self-hosted SFU and a separate LK Cloud project for inference can authenticate against the inference gateway with different credentials. This works correctly forinference/llm.ts,inference/tts.tsandinference/stt.ts, but is broken ininference/interruption/interruption_detector.ts: whenLIVEKIT_API_KEYis set (e.g. SFU creds),LIVEKIT_INFERENCE_API_KEYis silently ignored and the SDK signs the inference JWT with the SFU credentials, causingWebSocket connection rejected with status 401and a fallback to VAD-based interruption.Root cause
In
agents/src/inference/interruption/defaults.tstheinterruptionOptionDefaultsobject is initialised at module load with:```ts
apiKey: process.env.LIVEKIT_API_KEY || "",
apiSecret: process.env.LIVEKIT_API_SECRET || "",
```
Then in
agents/src/inference/interruption/interruption_detector.ts:```ts
const { apiKey, apiSecret, ... } = { ...interruptionOptionDefaults, ...options };
// ...
lkApiKey = apiKey ?? process.env.LIVEKIT_INFERENCE_API_KEY ?? process.env.LIVEKIT_API_KEY ?? '';
```
Because
apiKeyalready receivedprocess.env.LIVEKIT_API_KEYfrom the defaults, the??chain never falls back toLIVEKIT_INFERENCE_API_KEY.The other three inference plugins use the correct precedence with
||:```ts
// llm.ts, tts.ts, stt.ts
const lkApiKey = apiKey || process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY;
```
Reproduction
```
LIVEKIT_URL=ws://my-self-hosted:7880
LIVEKIT_API_KEY=A_key
LIVEKIT_API_SECRET=A_secret
LIVEKIT_INFERENCE_API_KEY=B_key
LIVEKIT_INFERENCE_API_SECRET=B_secret
```
Suggested fix
Align
defaults.tswith the precedence used by the other inference plugins:```ts
apiKey: process.env.LIVEKIT_INFERENCE_API_KEY || process.env.LIVEKIT_API_KEY || "",
apiSecret: process.env.LIVEKIT_INFERENCE_API_SECRET || process.env.LIVEKIT_API_SECRET || "",
```
Or alternatively keep
defaults.tsneutral (empty strings) and let the??chain ininterruption_detector.tsactually fall through to the env vars.Environment