Skip to content

Commit 876c983

Browse files
committed
fix
1 parent 0fd5e2d commit 876c983

2 files changed

Lines changed: 68 additions & 37 deletions

File tree

plugins/google/src/beta/realtime/api_proto.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export type LiveAPIModels =
2121
// Gemini API models
2222
| 'gemini-3.1-flash-live-preview' // https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-live-preview
2323
| 'gemini-2.5-flash-native-audio-preview-12-2025' // https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-live
24-
| 'gemini-2.5-flash-native-audio-preview-09-2025' // https://ai.google.dev/gemini-api/docs/models#gemini-2.5-flash-live
2524
| 'gemini-2.0-flash-exp'; // still works in Gemini API but not VertexAI
2625

2726
/**

plugins/google/src/beta/realtime/realtime_api.ts

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,19 @@ export class RealtimeModel extends llm.RealtimeModel {
303303
if (options.realtimeInputConfig?.automaticActivityDetection?.disabled) {
304304
serverTurnDetection = false;
305305
}
306+
// Environment variable fallbacks
307+
const apiKey = options.apiKey || process.env.GOOGLE_API_KEY;
308+
const project = options.project || process.env.GOOGLE_CLOUD_PROJECT;
309+
const location = options.location || process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
310+
const vertexai = options.vertexai ?? false;
311+
312+
// Model selection based on API type
313+
const defaultModel = vertexai
314+
? 'gemini-live-2.5-flash-native-audio'
315+
: 'gemini-2.5-flash-native-audio-preview-12-2025';
316+
317+
const model = options.model || defaultModel;
318+
const mutableSession = !model.includes('3.1');
306319

307320
super({
308321
messageTruncation: false,
@@ -311,25 +324,14 @@ export class RealtimeModel extends llm.RealtimeModel {
311324
autoToolReplyGeneration: true,
312325
audioOutput: options.modalities?.includes(Modality.AUDIO) ?? true,
313326
manualFunctionCalls: false,
314-
midSessionChatCtxUpdate: false,
315-
midSessionInstructionsUpdate: true,
327+
midSessionChatCtxUpdate: mutableSession,
328+
midSessionInstructionsUpdate: mutableSession,
316329
midSessionToolsUpdate: false,
317330
perResponseToolChoice: false,
318331
});
319332

320-
// Environment variable fallbacks
321-
const apiKey = options.apiKey || process.env.GOOGLE_API_KEY;
322-
const project = options.project || process.env.GOOGLE_CLOUD_PROJECT;
323-
const location = options.location || process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
324-
const vertexai = options.vertexai ?? false;
325-
326-
// Model selection based on API type
327-
const defaultModel = vertexai
328-
? 'gemini-live-2.5-flash-native-audio'
329-
: 'gemini-2.5-flash-native-audio-preview-12-2025';
330-
331333
this._options = {
332-
model: options.model || defaultModel,
334+
model,
333335
apiKey,
334336
voice: options.voice || 'Puck',
335337
language: options.language ? normalizeLanguage(options.language) : undefined,
@@ -547,10 +549,41 @@ export class RealtimeSession extends llm.RealtimeSession {
547549
}
548550

549551
async updateInstructions(instructions: string): Promise<void> {
550-
if (this.options.instructions === undefined || this.options.instructions !== instructions) {
551-
this.options.instructions = instructions;
552-
this.markRestartNeeded();
552+
if (this.options.instructions !== undefined && this.options.instructions === instructions) {
553+
return;
554+
}
555+
556+
this.options.instructions = instructions;
557+
558+
const unlock = await this.sessionLock.lock();
559+
try {
560+
if (!this.activeSession) {
561+
this.markRestartNeeded();
562+
return;
563+
}
564+
} finally {
565+
unlock();
566+
}
567+
568+
if (!this.realtimeModel.capabilities.midSessionInstructionsUpdate) {
569+
return;
553570
}
571+
572+
this.#logger.debug('Updating instructions mid-session');
573+
this.sendClientEvent({
574+
type: 'content',
575+
value: {
576+
turns: [
577+
{
578+
parts: [{ text: instructions }],
579+
// Vertex AI ignores role=None or role="system" and only works with role="model".
580+
// Gemini Live API (non-Vertex) errors on role="system"; role=None works as system role.
581+
role: this.options.vertexai ? 'model' : undefined,
582+
},
583+
],
584+
turnComplete: false,
585+
},
586+
});
554587
}
555588

556589
async updateChatCtx(chatCtx: llm.ChatContext): Promise<void> {
@@ -609,20 +642,21 @@ export class RealtimeSession extends llm.RealtimeSession {
609642
}
610643
}
611644

612-
this.sendClientEvent({
613-
type: 'content',
614-
value: {
615-
turns: turns as types.Content[],
616-
turnComplete: false,
617-
},
618-
});
619-
}
620-
621-
if (toolResults) {
622-
this.sendClientEvent({
623-
type: 'tool_response',
624-
value: toolResults,
625-
});
645+
if (toolResults) {
646+
this.sendClientEvent({
647+
type: 'tool_response',
648+
value: toolResults,
649+
});
650+
}
651+
if (this.realtimeModel.capabilities.midSessionChatCtxUpdate) {
652+
this.sendClientEvent({
653+
type: 'content',
654+
value: {
655+
turns: turns as types.Content[],
656+
turnComplete: false,
657+
},
658+
});
659+
}
626660
}
627661
}
628662

@@ -686,13 +720,11 @@ export class RealtimeSession extends llm.RealtimeSession {
686720
}
687721

688722
async generateReply(instructions?: string): Promise<llm.GenerationCreatedEvent> {
689-
if (this.options.model === 'gemini-3.1-flash-live-preview') {
723+
if (!this.realtimeModel.capabilities.midSessionChatCtxUpdate) {
690724
this.#logger.warn(
691-
'generateReply is not compatible with gemini-3.1-flash-live-preview. Use a Gemini 2.5 live model for voice-agent flows that require programmatic reply generation.',
692-
);
693-
throw new Error(
694-
"generateReply is not compatible with 'gemini-3.1-flash-live-preview'; use a Gemini 2.5 live model for voice-agent flows that require programmatic reply generation.",
725+
`generateReply is not compatible with '${this.options.model}' and will be ignored.`,
695726
);
727+
throw new Error(`generateReply is not compatible with '${this.options.model}'`);
696728
}
697729

698730
if (this.pendingGenerationFut && !this.pendingGenerationFut.done) {

0 commit comments

Comments
 (0)