Skip to content

Commit c55e7da

Browse files
committed
Merge branch 'feat/barge-in' into lukas/interruption-integration
2 parents c4d6774 + f1a2114 commit c55e7da

42 files changed

Lines changed: 383 additions & 208 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/shy-rockets-fry.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ Currently, only the following plugins are supported:
7676
| [@livekit/agents-plugin-livekit](https://www.npmjs.com/package/@livekit/agents-plugin-livekit) | EOU |
7777
| [@livekit/agents-plugin-anam](https://www.npmjs.com/package/@livekit/agents-plugin-anam) | Avatar |
7878
| [@livekit/agents-plugin-bey](https://www.npmjs.com/package/@livekit/agents-plugin-bey) | Avatar |
79+
| [@livekit/agents-plugin-xai](https://www.npmjs.com/package/@livekit/agents-plugin-xai) | LLM, TTS |
7980

8081
## Docs and guides
8182

agents/CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# @livekit/agents
22

3+
## 1.0.39
4+
5+
### Patch Changes
6+
7+
- update livekit inference model to match latest - [#993](https://github.com/livekit/agents-js/pull/993) ([@davidzhao](https://github.com/davidzhao))
8+
9+
- preserve thought_signature across parallel tool calls for Gemini 3+ for inference gateway - [#1000](https://github.com/livekit/agents-js/pull/1000) ([@toubatbrian](https://github.com/toubatbrian))
10+
11+
- Make agent state transition fixes and add interim transcript interruption support - [#992](https://github.com/livekit/agents-js/pull/992) ([@toubatbrian](https://github.com/toubatbrian))
12+
13+
- fix: handle VAD stream closed error during agent handover - [#997](https://github.com/livekit/agents-js/pull/997) ([@toubatbrian](https://github.com/toubatbrian))
14+
15+
- Fixed a race condition in `StreamAdapter` where `endInput()` could be called on an already-closed VAD stream during agent handover, causing an unrecoverable `stt_error`. This affected non-streaming STTs (like OpenAI STT) that use the StreamAdapter wrapper.
16+
- Added `isStreamClosedError()` utility function for consistent error handling.
17+
- Upgraded sharp from 0.34.3 to 0.34.5 to fix libvips version conflict (1.2.0 vs 1.2.4) that caused flaky agent behavior and ObjC class collision warnings on macOS.
18+
- Fixed pre-existing build error in test plugin (Int16Array to ArrayBuffer conversion).
19+
320
## 1.0.38
421

522
### Patch Changes

agents/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@livekit/agents",
3-
"version": "1.0.38",
3+
"version": "1.0.39",
44
"description": "LiveKit Agents - Node.js",
55
"main": "dist/index.js",
66
"require": "dist/index.cjs",
@@ -74,7 +74,7 @@
7474
"pidusage": "^4.0.1",
7575
"pino": "^8.19.0",
7676
"pino-pretty": "^11.0.0",
77-
"sharp": "0.34.3",
77+
"sharp": "0.34.5",
7878
"uuid": "^11.1.0",
7979
"ws": "^8.18.0",
8080
"zod-to-json-schema": "^3.24.6"

agents/src/inference/llm.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import { type AnyString, createAccessToken } from './utils.js';
1717
const DEFAULT_BASE_URL = 'https://agent-gateway.livekit.cloud/v1';
1818

1919
export type OpenAIModels =
20+
| 'openai/gpt-5.2'
21+
| 'openai/gpt-5.2-chat-latest'
22+
| 'openai/gpt-5.1'
23+
| 'openai/gpt-5.1-chat-latest'
2024
| 'openai/gpt-5'
2125
| 'openai/gpt-5-mini'
2226
| 'openai/gpt-5-nano'
@@ -28,19 +32,17 @@ export type OpenAIModels =
2832
| 'openai/gpt-oss-120b';
2933

3034
export type GoogleModels =
31-
| 'google/gemini-3-pro-preview'
32-
| 'google/gemini-3-flash-preview'
35+
| 'google/gemini-3-pro'
36+
| 'google/gemini-3-flash'
3337
| 'google/gemini-2.5-pro'
3438
| 'google/gemini-2.5-flash'
3539
| 'google/gemini-2.5-flash-lite'
3640
| 'google/gemini-2.0-flash'
3741
| 'google/gemini-2.0-flash-lite';
3842

39-
export type QwenModels = 'qwen/qwen3-235b-a22b-instruct';
43+
export type MoonshotModels = 'moonshotai/kimi-k2-instruct';
4044

41-
export type KimiModels = 'moonshotai/kimi-k2-instruct';
42-
43-
export type DeepSeekModels = 'deepseek-ai/deepseek-v3';
45+
export type DeepSeekModels = 'deepseek-ai/deepseek-v3' | 'deepseek-ai/deepseek-v3.2';
4446

4547
type ChatCompletionPredictionContentParam =
4648
Expand<OpenAI.Chat.Completions.ChatCompletionPredictionContent>;
@@ -80,13 +82,7 @@ export interface ChatCompletionOptions extends Record<string, unknown> {
8082
// response_format?: OpenAI.Chat.Completions.ChatCompletionCreateParams['response_format']
8183
}
8284

83-
export type LLMModels =
84-
| OpenAIModels
85-
| GoogleModels
86-
| QwenModels
87-
| KimiModels
88-
| DeepSeekModels
89-
| AnyString;
85+
export type LLMModels = OpenAIModels | GoogleModels | MoonshotModels | DeepSeekModels | AnyString;
9086

9187
export interface InferenceLLMOptions {
9288
model: LLMModels;
@@ -437,7 +433,10 @@ export class LLMStream extends llm.LLMStream {
437433
if (this.toolCallId && tool.id && tool.index !== this.toolIndex) {
438434
callChunk = this.createRunningToolCallChunk(id, delta);
439435
this.toolCallId = this.fncName = this.fncRawArguments = undefined;
440-
this.toolExtra = undefined;
436+
// Note: We intentionally do NOT reset toolExtra here.
437+
// For Gemini 3+, the thought_signature is only provided on the first tool call
438+
// in a parallel batch, but must be applied to ALL tool calls in the batch.
439+
// We preserve toolExtra so subsequent tool calls inherit the thought_signature.
441440
}
442441

443442
// Start or continue building the current tool call
@@ -447,9 +446,14 @@ export class LLMStream extends llm.LLMStream {
447446
this.fncName = tool.function.name;
448447
this.fncRawArguments = tool.function.arguments || '';
449448
// Extract extra from tool call (e.g., Google thought signatures)
450-
this.toolExtra =
449+
// Only update toolExtra if this tool call has extra_content.
450+
// Otherwise, inherit from previous tool call (for parallel Gemini tool calls).
451+
const newToolExtra =
451452
// eslint-disable-next-line @typescript-eslint/no-explicit-any
452453
((tool as any).extra_content as Record<string, unknown> | undefined) ?? undefined;
454+
if (newToolExtra) {
455+
this.toolExtra = newToolExtra;
456+
}
453457
} else if (tool.function.arguments) {
454458
this.fncRawArguments = (this.fncRawArguments || '') + tool.function.arguments;
455459
}
@@ -468,6 +472,7 @@ export class LLMStream extends llm.LLMStream {
468472
) {
469473
const callChunk = this.createRunningToolCallChunk(id, delta);
470474
this.toolCallId = this.fncName = this.fncRawArguments = undefined;
475+
// Reset toolExtra at the end of the response (not between parallel tool calls)
471476
this.toolExtra = undefined;
472477
return callChunk;
473478
}

agents/src/inference/stt.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,21 @@ import {
2525
import { type AnyString, connectWs, createAccessToken } from './utils.js';
2626

2727
export type DeepgramModels =
28-
| 'deepgram'
28+
| 'deepgram/flux-general'
2929
| 'deepgram/nova-3'
30-
| 'deepgram/nova-3-general'
3130
| 'deepgram/nova-3-medical'
32-
| 'deepgram/nova-2-conversationalai'
3331
| 'deepgram/nova-2'
34-
| 'deepgram/nova-2-general'
3532
| 'deepgram/nova-2-medical'
33+
| 'deepgram/nova-2-conversationalai'
3634
| 'deepgram/nova-2-phonecall';
3735

38-
export type CartesiaModels = 'cartesia' | 'cartesia/ink-whisper';
36+
export type CartesiaModels = 'cartesia/ink-whisper';
37+
38+
export type AssemblyaiModels =
39+
| 'assemblyai/universal-streaming'
40+
| 'assemblyai/universal-streaming-multilingual';
3941

40-
export type AssemblyaiModels = 'assemblyai' | 'assemblyai/universal-streaming';
42+
export type ElevenlabsSTTModels = 'elevenlabs/scribe_v2_realtime';
4143

4244
export interface CartesiaOptions {
4345
min_volume?: number; // default: not specified
@@ -77,7 +79,7 @@ export type STTLanguages =
7779
| 'hi'
7880
| AnyString;
7981

80-
type _STTModels = DeepgramModels | CartesiaModels | AssemblyaiModels;
82+
type _STTModels = DeepgramModels | CartesiaModels | AssemblyaiModels | ElevenlabsSTTModels;
8183

8284
export type STTModels = _STTModels | 'auto' | AnyString;
8385

agents/src/inference/tts.ts

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,27 @@ import {
2323
import { type AnyString, connectWs, createAccessToken } from './utils.js';
2424

2525
export type CartesiaModels =
26-
| 'cartesia'
27-
| 'cartesia/sonic'
26+
| 'cartesia/sonic-3'
2827
| 'cartesia/sonic-2'
29-
| 'cartesia/sonic-turbo';
28+
| 'cartesia/sonic-turbo'
29+
| 'cartesia/sonic';
30+
31+
export type DeepgramTTSModels = 'deepgram/aura' | 'deepgram/aura-2';
3032

3133
export type ElevenlabsModels =
32-
| 'elevenlabs'
3334
| 'elevenlabs/eleven_flash_v2'
3435
| 'elevenlabs/eleven_flash_v2_5'
3536
| 'elevenlabs/eleven_turbo_v2'
3637
| 'elevenlabs/eleven_turbo_v2_5'
3738
| 'elevenlabs/eleven_multilingual_v2';
3839

39-
export type RimeModels = 'rime' | 'rime/mist' | 'rime/mistv2' | 'rime/arcana';
40+
export type InworldModels =
41+
| 'inworld/inworld-tts-1.5-max'
42+
| 'inworld/inworld-tts-1.5-mini'
43+
| 'inworld/inworld-tts-1-max'
44+
| 'inworld/inworld-tts-1';
4045

41-
export type InworldModels = 'inworld' | 'inworld/inworld-tts-1';
46+
export type RimeModels = 'rime/arcana' | 'rime/mistv2';
4247

4348
export interface CartesiaOptions {
4449
duration?: number; // max duration of audio in seconds
@@ -50,25 +55,40 @@ export interface ElevenlabsOptions {
5055
apply_text_normalization?: 'auto' | 'off' | 'on'; // default: "auto"
5156
}
5257

58+
export interface DeepgramTTSOptions {}
59+
5360
export interface RimeOptions {}
5461

5562
export interface InworldOptions {}
5663

57-
type _TTSModels = CartesiaModels | ElevenlabsModels | RimeModels | InworldModels;
58-
59-
export type TTSModels = CartesiaModels | ElevenlabsModels | RimeModels | InworldModels | AnyString;
64+
type _TTSModels =
65+
| CartesiaModels
66+
| DeepgramTTSModels
67+
| ElevenlabsModels
68+
| RimeModels
69+
| InworldModels;
70+
71+
export type TTSModels =
72+
| CartesiaModels
73+
| DeepgramTTSModels
74+
| ElevenlabsModels
75+
| RimeModels
76+
| InworldModels
77+
| AnyString;
6078

6179
export type ModelWithVoice = `${_TTSModels}:${string}` | TTSModels;
6280

6381
export type TTSOptions<TModel extends TTSModels> = TModel extends CartesiaModels
6482
? CartesiaOptions
65-
: TModel extends ElevenlabsModels
66-
? ElevenlabsOptions
67-
: TModel extends RimeOptions
68-
? RimeOptions
69-
: TModel extends InworldOptions
70-
? InworldOptions
71-
: Record<string, unknown>;
83+
: TModel extends DeepgramTTSModels
84+
? DeepgramTTSOptions
85+
: TModel extends ElevenlabsModels
86+
? ElevenlabsOptions
87+
: TModel extends RimeModels
88+
? RimeOptions
89+
: TModel extends InworldModels
90+
? InworldOptions
91+
: Record<string, unknown>;
7292

7393
type TTSEncoding = 'pcm_s16le';
7494

agents/src/stt/stream_adapter.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import type { AudioFrame } from '@livekit/rtc-node';
55
import { log } from '../log.js';
66
import type { APIConnectOptions } from '../types.js';
7+
import { isStreamClosedError } from '../utils.js';
78
import type { VAD, VADStream } from '../vad.js';
89
import { VADEventType } from '../vad.js';
910
import type { SpeechEvent } from './stt.js';
@@ -68,7 +69,17 @@ export class StreamAdapterWrapper extends SpeechStream {
6869
this.#vadStream.pushFrame(input);
6970
}
7071
}
71-
this.#vadStream.endInput();
72+
73+
// Guard against calling endInput() on already-closed stream
74+
// This happens during handover when close() is called while forwardInput is running
75+
try {
76+
this.#vadStream.endInput();
77+
} catch (e) {
78+
if (isStreamClosedError(e)) {
79+
return;
80+
}
81+
throw e;
82+
}
7283
};
7384

7485
const recognize = async () => {

agents/src/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,20 @@ export class InvalidErrorType extends Error {
675675
}
676676
}
677677

678+
/**
679+
* Check if an error is a stream closed error that can be safely ignored during cleanup.
680+
* This happens during handover/cleanup when close() is called while operations are still running.
681+
*
682+
* @param error - The error to check.
683+
* @returns True if the error is a stream closed error.
684+
*/
685+
export function isStreamClosedError(error: unknown): boolean {
686+
return (
687+
error instanceof Error &&
688+
(error.message === 'Stream is closed' || error.message === 'Input is closed')
689+
);
690+
}
691+
678692
/**
679693
* In JS an error can be any arbitrary value.
680694
* This function converts an unknown error to an Error and stores the original value in the error object.

agents/src/voice/agent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,16 +338,16 @@ export class Agent<UserData = any> {
338338
);
339339
}
340340

341-
// TODO(brian): make parallelToolCalls configurable
342341
const { toolChoice } = modelSettings;
343342
const connOptions = activity.agentSession.connOptions.llmConnOptions;
344343

344+
// parallelToolCalls is not passed here - it will use the value from LLM's modelOptions
345+
// This allows users to configure it via: new inference.LLM({ modelOptions: { parallel_tool_calls: false } })
345346
const stream = activity.llm.chat({
346347
chatCtx,
347348
toolCtx,
348349
toolChoice,
349350
connOptions,
350-
parallelToolCalls: true,
351351
});
352352

353353
let cleaned = false;

0 commit comments

Comments
 (0)